Documentation
¶
Overview ¶
Package mcp implements the MCP server mode: a thin, stateless adapter that exposes the same core sensors as MCP tools (PRD section 10). It reimplements no audit logic — every tool call translates to the same engine the CLI uses — and relies on prompt caching to neutralize the stateless overhead.
Status: BUILT. NewServer registers the tool set and Serve runs it over the stdio transport; every tool is a thin adapter onto a core handler. The HTTP/SSE transport is the one part still unbuilt — `mcp serve --port` returns an explicit error rather than falling back silently.
Index ¶
- Constants
- func NewServer() *mcpsdk.Server
- func Serve(ctx context.Context) error
- func SupportedLanguageNames() []string
- type ActionableSection
- type BaselineAcceptRequest
- type BaselineAcceptResponse
- type BaselineDelta
- type BaselineListRequest
- type BaselineListResponse
- type BaselinePruneRequest
- type BaselinePruneResponse
- type BaselineRegisterAuthzHelperRequest
- type BaselineRegisterAuthzHelperResponse
- type BaselineUnregisterAuthzHelperRequest
- type BaselineUnregisterAuthzHelperResponse
- type BudgetBlock
- type CheckCVEsRequest
- type CheckCVEsResponse
- type ConfirmSurfaceRequest
- type ConfirmSurfaceResponse
- type CoverageRequest
- type CoverageResponse
- type DBSection
- type DBSummary
- type FileInput
- type FrontierPending
- type GoneItem
- type ResolvedClean
- type ScanAllRequest
- type ScanAllResponse
- type ScanAllSummary
- type ScanDBRequest
- type ScanDBResponse
- type ScanEndpointRequest
- type ScanEndpointResponse
- type ScanRequest
- type ScanResponse
- type ScopeBlock
- type SecuritySection
- type SecuritySummary
- type SummaryTotals
- type SurfaceIDORRequest
- type SurfaceResponse
- type Tool
- type VulnerableDependency
Constants ¶
const ( ScopeModeFull = "full" ScopeModePartial = "partial" )
The two scan modes. A response ALWAYS carries one of them: the field's presence is not conditional, so a consumer never has to infer the mode from an absence.
const ResponseBudgetBytes = 40_000
ResponseBudgetBytes is the byte budget codefit-scan-all declares for its own serialized response.
What is MEASURED (roadmap P0-4): a bisection run against a real MCP client (Claude Code, 2026-08-09), driving the v0.2.6 binary over stdio with controlled-size responses cut from trimmed copies of a real 317-file project. The real ceiling was bracketed, not pinpointed:
64 097 bytes ACCEPTED <- largest observed acceptance 74 195 bytes REJECTED "exceeds maximum allowed tokens"
40 000 was CHOSEN inside that bracket, not measured directly: 62% of the largest observed acceptance (64 097), leaving room for roughly a 60% increase in token density before approaching the rejected end (74 195), and it matches an earlier, independent data point — a 40 282-byte response that is known to have arrived (2026-08-04, before this bisection existed).
The assumption this number rests on, stated plainly: the client's limit is in TOKENS; this budget counts BYTES. The bytes-per-token ratio is content-dependent (identifiers, hex digests and deep paths run denser than prose), so the margin above is NOT fixed — a byte count under budget can still cross a token ceiling the same client would reject. And the measurement is of ONE client, ONE date, ONE content shape: other MCP clients (Cursor, VSCode, OpenCode) have their own limits, unmeasured here.
Measured consequence of moving this number down (2026-08-09, same real project, fresh baseline): payload 39 962 bytes, 19 of 174 endpoints withheld (5 actionable, 14 frontier_pending) — at the old 60 000 this same project fit entirely with 0 withheld. Real mid-sized projects now see a non-zero withheld count where they previously did not; each bucket's `count` stays the complete number and codefit-scan-endpoint still fetches full detail on request (ADR 0054), but this is a user-visible behaviour change, not a free tightening.
See ADR 0062 for the full record, including what this number does NOT fix: a byte budget cannot guarantee a token limit. The structural answer — a hard cap on entries per bucket, so response size stops being a function of project size — is roadmap P0-4's declared follow-up, not this change.
The budget is DECLARED in the response (see BudgetBlock) and enforced by withholding the lowest-ranked endpoints, never by truncating the payload: a clipped response that reads like a complete one is the one outcome forbidden (ADR 0054, same principle as ADR 0048).
Variables ¶
This section is empty.
Functions ¶
func NewServer ¶
NewServer builds the codefit MCP server with its tools registered. Each tool is a THIN adapter: it hands the SDK's typed request to the core handler that already exists and is tested, and returns the core's result as structured output. No audit logic lives here — the MCP layer only connects the protocol to the engine (PRD §15). The server is stateless: every tool call is independent and carries everything it needs.
func Serve ¶
Serve runs the codefit MCP server over the stdio transport until ctx is cancelled. (HTTP/SSE is deferred; the SDK abstracts the transport, so it is added later without a refactor.)
func SupportedLanguageNames ¶ added in v0.2.8
func SupportedLanguageNames() []string
SupportedLanguageNames returns the canonical, deduplicated, sorted set of language names codefit-scan-all can resolve a security provider for — DERIVED from registry.ExposedForSecurity(), never a hand-written literal. This is the single source the nothing-measurable error message reads (D4/D5): a language and its aliases (ts/tsx) collapse to one canonical name here.
Types ¶
type ActionableSection ¶ added in v0.2.7
type ActionableSection struct {
Count int `json:"count"`
Withheld int `json:"withheld"`
Note string `json:"note,omitempty"`
// Endpoints is the rendered prefix of the ranked list: hardest gap kind first
// (affirmed → access → exposure → efficiency), then by certain-concern count.
Endpoints []report.ActionableEndpoint `json:"endpoints,omitempty"`
}
ActionableSection declares the endpoints codefit resolved locally AND found a gap in — the ones the agent acts on. They are NAMED with what it takes to rank them (counts, categories, gap kinds, best certainty, whether an affirmation is present) and their deterministic concerns in full; the surface question and signals text is one codefit-scan-endpoint call away.
Count is the COMPLETE number codefit classified as actionable and never the number rendered — Withheld accounts for the difference. Reading Count off len(Endpoints) is exactly the bug this section's shape exists to make impossible.
type BaselineAcceptRequest ¶
type BaselineAcceptRequest struct {
Root string `json:"root"`
Fingerprints []string `json:"fingerprints"`
Reason string `json:"reason"`
}
BaselineAcceptRequest marks baseline items as acknowledged by a human (a false positive or accepted debt). reason is mandatory. SAFETY: the agent must call this ONLY when the human decided so — codefit records by:"human" but cannot verify it; the skill enforces the discipline.
type BaselineAcceptResponse ¶
type BaselineAcceptResponse struct {
Accepted []string `json:"accepted"`
Note string `json:"note"`
}
BaselineAcceptResponse reports which fingerprints were acknowledged.
func HandleBaselineAccept ¶
func HandleBaselineAccept(req BaselineAcceptRequest) (BaselineAcceptResponse, error)
HandleBaselineAccept records a human's decision to accept items. It only touches the baseline file.
type BaselineDelta ¶
type BaselineDelta struct {
New int `json:"new"`
Changed int `json:"changed"`
Known int `json:"known"`
Acknowledged int `json:"acknowledged"`
Gone int `json:"gone"`
AffirmationsShown int `json:"affirmations_shown"`
GoneCandidates []GoneItem `json:"gone_candidates,omitempty"`
Note string `json:"note"`
}
BaselineDelta is scan-all's account of how the current scan compares to the committed baseline. The agent acts on new + changed (and unaccepted affirmations); known surface is silenced but counted.
type BaselineListRequest ¶
type BaselineListRequest struct {
Root string `json:"root"`
Language string `json:"language,omitempty"`
Filter string `json:"filter,omitempty"`
}
BaselineListRequest reads the current baseline so the agent can reference items in accept/prune WITHOUT reading the raw .codefit-baseline file. Filter is "" (all), "known" (not yet accepted), or "acknowledged". Language is accepted for tool uniformity but unused — listing reads the file, it does not scan code.
type BaselineListResponse ¶
type BaselineListResponse struct {
Items []baseline.Entry `json:"items"`
Count int `json:"count"`
AuthzHelpers []baseline.AuthzHelper `json:"authz_helpers,omitempty"`
Note string `json:"note"`
}
BaselineListResponse is the projected baseline: per item just fp+file+category+ state (+reason/date if acknowledged), small enough not to truncate. AuthzHelpers lists the project's registered custom authz helpers (read-only visibility) so the agent can see — and propose unregistering — them without reading the file.
func HandleBaselineList ¶
func HandleBaselineList(req BaselineListRequest) (BaselineListResponse, error)
HandleBaselineList returns the baseline entries. A missing baseline is NOT an error: it returns an empty list with a note pointing to scan-all. Read-only.
type BaselinePruneRequest ¶
type BaselinePruneRequest struct {
Root string `json:"root"`
Language string `json:"language"`
Fingerprints []string `json:"fingerprints,omitempty"`
}
BaselinePruneRequest removes baseline items that no longer exist in the code (gone). With Fingerprints set it prunes only those (if confirmed gone); empty prunes all confirmed-gone items.
type BaselinePruneResponse ¶
BaselinePruneResponse reports which fingerprints were pruned.
func HandleBaselinePrune ¶
func HandleBaselinePrune(req BaselinePruneRequest) (BaselinePruneResponse, error)
HandleBaselinePrune re-scans to confirm which baseline items are gone, then removes them. Stateless: it recomputes the current surface; it never edits code.
type BaselineRegisterAuthzHelperRequest ¶ added in v0.1.2
type BaselineRegisterAuthzHelperRequest struct {
Root string `json:"root"`
Language string `json:"language"`
HelperName string `json:"helper_name"`
Reason string `json:"reason"`
}
BaselineRegisterAuthzHelperRequest registers a project-specific authz helper so codefit recognizes it on later scans (known_authz_detected reflects it). reason is mandatory. SAFETY: registering silences the AUTHZ gap on EVERY item that calls the helper — far more reach than accepting one item. The agent must call this ONLY when the human approved it; codefit records by:"human" but cannot verify it (the skill enforces the discipline). It does NOT clear the IDOR/ownership gap (ADR 0013, ADR 0006 amended).
type BaselineRegisterAuthzHelperResponse ¶ added in v0.1.2
type BaselineRegisterAuthzHelperResponse struct {
Registered bool `json:"registered"`
Note string `json:"note"`
}
BaselineRegisterAuthzHelperResponse reports the outcome.
func HandleBaselineRegisterAuthzHelper ¶ added in v0.1.2
func HandleBaselineRegisterAuthzHelper(req BaselineRegisterAuthzHelperRequest) (BaselineRegisterAuthzHelperResponse, error)
HandleBaselineRegisterAuthzHelper records a human-approved custom authz helper in the baseline. It only touches the baseline file.
type BaselineUnregisterAuthzHelperRequest ¶ added in v0.1.2
type BaselineUnregisterAuthzHelperRequest struct {
Root string `json:"root"`
Language string `json:"language"`
HelperName string `json:"helper_name"`
}
BaselineUnregisterAuthzHelperRequest reverses a registration (the developer's decision is always reversible). The next scan stops recognizing the helper.
type BaselineUnregisterAuthzHelperResponse ¶ added in v0.1.2
type BaselineUnregisterAuthzHelperResponse struct {
Unregistered bool `json:"unregistered"`
Note string `json:"note"`
}
BaselineUnregisterAuthzHelperResponse reports the outcome.
func HandleBaselineUnregisterAuthzHelper ¶ added in v0.1.2
func HandleBaselineUnregisterAuthzHelper(req BaselineUnregisterAuthzHelperRequest) (BaselineUnregisterAuthzHelperResponse, error)
HandleBaselineUnregisterAuthzHelper removes a registered helper. It only touches the baseline file.
type BudgetBlock ¶ added in v0.2.7
type BudgetBlock struct {
Bytes int `json:"bytes"`
Withheld int `json:"withheld"`
Ordering string `json:"ordering"`
Note string `json:"note"`
}
BudgetBlock is scan-all's account of its own size. codefit's primary tool has to RETURN: a response an MCP client refuses is worth less than a smaller one that arrives. So the response declares the budget it is written to, and when the endpoint list does not fit it says exactly how many endpoints it is not showing and on what ordering it kept the ones it shows.
Withheld=0 still carries a Note, on purpose. "No mention of truncation" and "nothing was truncated" must not be the same bytes on the wire: that ambiguity is how a clipped response comes to read like a complete one (ADR 0048).
type CheckCVEsRequest ¶ added in v0.1.4
type CheckCVEsRequest struct {
Root string `json:"root"`
}
CheckCVEsRequest is the input to codefit-check-cves: a project root. The dependency manifests are auto-detected under it.
type CheckCVEsResponse ¶ added in v0.1.4
type CheckCVEsResponse struct {
Vulnerable []VulnerableDependency `json:"vulnerable"`
DependenciesScanned int `json:"dependencies_scanned"`
Notes []string `json:"notes"`
}
CheckCVEsResponse reports the vulnerable dependencies, how many were scanned, and any honest notes (a manifest present without its lockfile, or none found).
func HandleCheckCVEs ¶ added in v0.1.4
func HandleCheckCVEs(req CheckCVEsRequest) (CheckCVEsResponse, error)
HandleCheckCVEs reads the project's dependency manifests (exact versions from lockfiles / go.mod only), queries OSV.dev for known vulnerabilities, and returns the vulnerable dependencies. A thin adapter: it parses manifests, queries the client, and reshapes the result — no detection logic here. When no version resolves (no lockfile), it returns the honest note WITHOUT querying OSV.
type ConfirmSurfaceRequest ¶
type ConfirmSurfaceRequest struct {
Confirmations []surface.Confirmation `json:"confirmations"`
}
ConfirmSurfaceRequest carries the agent's verdicts back to codefit.
type ConfirmSurfaceResponse ¶
type ConfirmSurfaceResponse struct {
Findings []findings.Finding `json:"findings"`
Dismissed []surface.Confirmation `json:"dismissed"`
Uncertain []surface.Confirmation `json:"uncertain"`
Invalid []surface.Confirmation `json:"invalid"`
}
ConfirmSurfaceResponse is the integration result: probabilistic findings plus the traceability buckets. Stateless — built purely from the verdicts sent.
func HandleConfirmSurface ¶
func HandleConfirmSurface(req ConfirmSurfaceRequest) ConfirmSurfaceResponse
HandleConfirmSurface integrates the agent's verdicts: codefit-confirm-surface. It recomputes each surface id to validate the verdict before integrating, and keeps no session.
type CoverageRequest ¶
type CoverageRequest struct {
Language string `json:"language"`
// Detail names the entries whose full prose the caller wants. Many ids in
// one call: an agent that has to make one round trip per declared limit
// stops asking. Omit it and the answer is the index alone.
Detail []string `json:"detail,omitempty"`
}
CoverageRequest is the input to codefit-coverage.
type CoverageResponse ¶
type CoverageResponse struct {
Language string `json:"language"`
// Index holds EVERY entry the manifest carries, always. The response budget
// authorizes withholding for scan-all; for coverage it authorizes nothing,
// so an over-budget index is reported as over budget and stays complete.
Index []coverage.IndexEntry `json:"index"`
// Entries, Bytes and IndexBytes let the caller check the facts it would
// otherwise have to take on trust: how many entries arrived, and how large
// this response's payload was on the wire.
//
// Bytes is the WHOLE payload — the index plus any detail that was asked for
// — and never the index alone. It used to be the index alone, which made a
// detail request answer with a size verdict scoped to something smaller than
// what it was returning: a 182,440-byte response declaring 21,951 bytes and
// "within budget". A response that misreports its own size is the defect
// PR #128 fixed for scan-all's budget note, and it is not allowed here either.
Entries int `json:"entries"`
Bytes int `json:"bytes"`
IndexBytes int `json:"index_bytes"`
// Withheld is always zero, and WithheldNote says so in words. "No mention of
// truncation" and "nothing was truncated" are not the same bytes, and an
// agent cannot tell a complete answer from a quiet one without being told.
Withheld int `json:"withheld"`
WithheldNote string `json:"withheld_note"`
// OverBudget reports that the index exceeded the response budget. Nothing is
// dropped when it does — the instruction is to shorten a claim.
OverBudget bool `json:"over_budget,omitempty"`
BudgetNote string `json:"budget_note,omitempty"`
// Detail carries the full prose of the requested entries, byte for byte as
// authored.
Detail []coverage.Entry `json:"detail,omitempty"`
// Unrecognized names every requested id that matched no entry. Naming it is
// the point: an empty success would say the entry has nothing to declare,
// which is a different and false answer from "there is no such entry".
Unrecognized []string `json:"unrecognized,omitempty"`
UnrecognizedNote string `json:"unrecognized_note,omitempty"`
// Derived is true when the answer was COMPUTED from the provider's
// Capability() rather than served from a hand-written CoverageManifest()
// (R1, docs/specs/declared-partial-language-exposure.md): the derived
// answer is the FLOOR every registered, exposed language gets; a
// hand-written prose manifest (TypeScript today) stays authoritative and
// is never replaced by this.
Derived bool `json:"derived"`
}
CoverageResponse carries the coverage index: every entry codefit declares for the language, as an id, a one-line claim, its answer class, and whether it has more prose to give. The prose itself is served only when asked for by id.
func HandleCoverage ¶
func HandleCoverage(req CoverageRequest) (CoverageResponse, error)
HandleCoverage returns the coverage manifest for the language. A provider with a hand-written CoverageManifest() serves it unchanged (Derived: false) — the prose manifest stays authoritative. A registered, resolvable provider with NO prose manifest still gets a truthful, DERIVED answer from its Capability() (Derived: true) — "no coverage manifest for language X" is the wrong answer to "what do you cover", not the absence of an error. Only a language that resolves NO provider at all (unregistered, or registered but not exposed for security scanning) still errors: there is nothing to derive from.
type DBSection ¶ added in v0.2.0
type DBSection struct {
Measured bool `json:"measured"`
Note string `json:"note,omitempty"`
Findings []findings.Finding `json:"findings,omitempty"`
Surface []surfaceindex.Entry `json:"surface,omitempty"`
// Count is the COMPLETE number of surface items this section classifies —
// taken from surfaceindex.Index's own return, computed independently of how
// Surface is built, never from len(Surface) after the fact (design D4/I4:
// reading it back off the rendered index is the self-referential trap the
// coverage-chain archive records, obs #1664).
Count int `json:"count"`
// Withheld is always 0: there is no ranking axis across db surface's 18
// disjoint categories (no severity field) to withhold BY — a stated absence
// of a mechanism, not a principle (design D4). WithheldNote says so in
// words, deliberately NOT coverage's sentence (a different reason) and NOT
// the endpoint-bucket pattern (db.surface is never partially rendered).
Withheld int `json:"withheld"`
WithheldNote string `json:"withheld_note,omitempty"`
Score int `json:"score"`
}
DBSection is the database dimension's result inside scan-all. Measured=false with a Note is the honest "not audited" state (disabled / no parser / schema read or parse failure) — a db failure is SOFT here, reported but never fatal to the security result (ADR 0020). Findings are affirmations (e.g. DB-050); Surface is the LIGHT index (surfaceindex.Entry, not the full findings.SurfaceItem — design D1/D5) of the questions; both are already filtered by the baseline. Full detail of any named item is one codefit-scan-db call away with `detail: [ids]`.
type DBSummary ¶ added in v0.2.9
type DBSummary struct {
SchemaSources int `json:"schema_sources"`
DeterministicFindings int `json:"deterministic_findings"`
SurfaceItems int `json:"surface_items"`
}
DBSummary is the database dimension's own counts.
SchemaSources is the dimension's scale unit — the distinct schema sources this pass READ, the same census scope's denominator takes (one local, shared; two call sites computing one census is how this repo drifts). It is not a new measurement, and under a narrowed scope it shrinks with what was read, exactly like `scope`.
There is deliberately NO certain_concerns here. The security field of that name counts Deterministic PLUS SurfaceConfirmed concerns (core/report/aggregate.go's countCertain), where SurfaceConfirmed is set from a security-surface structural fact DB items never carry — so it is not a certainty-1.0 count, and a DB sibling under the same name would be the exact same-name-different-definition defect this shape exists to fix. Adding the key later is additive; shipping a differently-defined one now would be breaking. See ADR 0069.
type FileInput ¶
FileInput is one source file a surface tool reasons over (path + content), so the tool stays stateless — the caller passes the bytes, codefit reads nothing.
type FrontierPending ¶
type FrontierPending struct {
Count int `json:"count"`
Withheld int `json:"withheld"`
Note string `json:"note,omitempty"`
Endpoints []report.FrontierEndpoint `json:"endpoints,omitempty"`
}
FrontierPending declares the endpoints codefit did NOT resolve locally: the data left the handler body, so codefit concluded nothing and the agent must follow it in the code. They are named (not detailed) with a Note explaining why they are not detailed and how to fetch any of them. This is not hiding — it is prioritising while declaring the rest.
type GoneItem ¶
type GoneItem struct {
Fingerprint string `json:"fingerprint"`
Category string `json:"category"`
File string `json:"file"`
Snippet string `json:"snippet,omitempty"`
}
GoneItem names a baseline item no longer present in the code — a prune candidate.
type ResolvedClean ¶
type ResolvedClean struct {
Count int `json:"count"`
Withheld int `json:"withheld"`
Note string `json:"note,omitempty"`
Endpoints []report.ResolvedCleanEndpoint `json:"endpoints,omitempty"`
}
ResolvedClean declares the endpoints codefit resolved locally and found clean (controls present, no gap). They are NAMED with a verification fact, not detailed. This is an affirmation — codefit looked and it is clean — not a generic "not detailed" bucket; that is why it is separate from FrontierPending.
type ScanAllRequest ¶
type ScanAllRequest struct {
Root string `json:"root"`
Language string `json:"language"`
// ChangedFiles narrows the audit to these project-relative paths (layer 0 of
// the filtering pyramid). codefit does not ask git which files changed — it
// has no power over the user's git, and the calling agent already knows what
// it touched. Absent or empty means a FULL audit, never "audit nothing".
//
// A narrowed run declares itself in the response's scope block, cannot mark a
// baseline item in an unopened file as gone, and leaves the DB dimension NOT
// MEASURED unless a configured schema path is in scope.
ChangedFiles []string `json:"changed_files,omitempty"`
}
ScanAllRequest is the input to codefit-scan-all: a project root and language. codefit walks the project, runs the deterministic sensor and the surface queries, and returns the complete per-endpoint picture.
type ScanAllResponse ¶
type ScanAllResponse struct {
// Summary is the per-dimension count block: one sub-block per audit
// dimension plus a derived totals, each count declaring which dimension it
// counted. A null sub-block means that dimension was not measured; see
// ScanAllSummary for why an unqualified count was a defect.
Summary ScanAllSummary `json:"summary"`
// Scope declares how much of the project this response describes: mode full or
// partial, how many auditable files were in scope, and which requested paths
// the audit never reached. It is ALWAYS present, so a consumer never infers
// the mode from an absence and never reads a partial `blocked: false` as the
// wider claim it is not.
Scope ScopeBlock `json:"scope"`
// Score is the per-dimension breakdown plus the weighted global (ADR 0021). It
// is ALWAYS present: by_dimension carries every weighted dimension, with the
// unaudited ones (review/complexity/tests, and db when it did not run) as null —
// an honest statement that the dimension exists but was not measured. The score
// reflects deterministic AFFIRMATIONS only, not mapped surface.
Score scoring.ScoreSummary `json:"score"`
Baseline BaselineDelta `json:"baseline"`
// Budget declares the response's own byte budget and whether anything was
// withheld to meet it. ALWAYS present, including when nothing was withheld: an
// agent must be able to tell a complete list from a cut one by reading the
// response, never by guessing (ADR 0054).
Budget BudgetBlock `json:"budget"`
Actionable ActionableSection `json:"actionable"`
ResolvedClean ResolvedClean `json:"resolved_clean"`
FrontierPending FrontierPending `json:"frontier_pending"`
// Security reports whether the security dimension ran (D3b). It is ALWAYS
// present — deliberately NOT `omitempty`, unlike DB: the db dimension may
// legitimately not apply to a project (no database configured), but security
// applies to every project, so an ABSENT section could only mean an older
// codefit build. Measured=false with a Note is the honest "no provider for
// this language" state, mirroring DBSection's own Measured/Note shape.
Security SecuritySection `json:"security"`
// DB is the parallel database-structure section — the db dimension's findings
// and surface, baseline-filtered. It is NON-endpoint (a table has no route), so
// it is its own section, not one of the three endpoint buckets. Nil when the
// project has no database.schema_paths configured, so a project without a
// database yields a response byte-identical to before db was wired (ADR 0020).
DB *DBSection `json:"db,omitempty"`
}
ScanAllResponse is the agent-first synthesis as an ACTIONABLE summary, not the raw item dump. It has three buckets, one per resolution level, all decided by facts codefit already computes (ADR 0008):
- Actionable — resolved locally AND has a gap: full detail, the agent acts.
- ResolvedClean — resolved locally, NO gap: named + a verification fact; codefit checked and the controls are present.
- FrontierPending — not resolved locally (the data left the handler body): named; the agent follows it in the code.
ResolvedClean and FrontierPending are kept DISTINCT on purpose: one affirms codefit verified the controls, the other states codefit could not conclude — epistemological opposites the agent must distinguish (flattening them would be the old frontier-wording error). Full detail of any named endpoint is always one codefit-scan-endpoint call away. Note on the baseline layer: when a baseline exists, the three buckets are FILTERED to what is not yet tracked (new/changed surface, and unaccepted deterministic affirmations). So on an unchanged re-scan all three buckets can be empty even though Summary.Security.CertainConcerns (computed before filtering) is > 0 — the difference is exactly the "known" surface the baseline is silencing.
func HandleScanAll ¶
func HandleScanAll(req ScanAllRequest) (ScanAllResponse, error)
HandleScanAll runs the full audit over the project and returns the actionable summary plus the named frontier-pending list. It reuses the real security sensor (the deterministic rules plus the three surface queries already run together there), groups the result by endpoint, and partitions by the local-resolution fact — it adds no detection, only the aggregation and the split.
type ScanAllSummary ¶
type ScanAllSummary struct {
// Security counts the security dimension only. Nil when no provider
// resolved for the language — never a zeroed block, which would read as
// "codefit looked at the code and found nothing".
Security *SecuritySummary `json:"security"`
// DB counts the database dimension only. Nil when the dimension did not
// run (no database.schema_paths configured, or narrowed out of scope) and
// when it ran but could not measure (no parser, unreadable schema) — the
// DBSection.Measured=false state.
DB *DBSummary `json:"db"`
// Totals is DERIVED from the non-nil sub-blocks, never written by hand: a
// hand-kept total drifts the first time a dimension is added. It carries
// only COMMENSURABLE units — a deterministic finding and a mapped surface
// item mean the same thing in both dimensions. Endpoints and schema
// sources are each dimension's own scale unit and are summed nowhere: a
// table has no route.
Totals SummaryTotals `json:"totals"`
// Note is ALWAYS present, on the BudgetBlock precedent: "no mention" and
// "nothing to mention" must not be the same bytes on the wire.
Note string `json:"note"`
}
ScanAllSummary is the at-a-glance count, not a judgment — and every count in it DECLARES the dimension it counted.
The flat shape this replaced had four unqualified fields (endpoints, deterministic_findings, surface_items, certain_concerns) that were all functions of the SECURITY sensor's result while presenting themselves as the response's summary. A DB-heavy project therefore read `surface_items: 0` over a db.surface holding dozens of items: a security-only prefix of the truth, presented unlabelled as the whole (invariant I4 of docs/specs/audit-protocol.md), producing the one thing this project exists to prevent — a zero that means "nobody looked" (I2).
A sub-block is a POINTER and deliberately NOT omitempty: the key is always on the wire, and `null` is the statement "this dimension was not measured". That is the shape score.by_dimension already ships (`"db": null` beside `"db": 95`); an ABSENT key would be a third state a reader has to guess at.
type ScanDBRequest ¶ added in v0.2.0
type ScanDBRequest struct {
Root string `json:"root"`
Language string `json:"language"`
Detail []string `json:"detail,omitempty"`
}
ScanDBRequest is the input to codefit-scan-db: a project root and language. Detail names surface item ids (from a prior Surface index, or from scan-all's db.surface) whose full findings.SurfaceItem the caller wants — mirrors CoverageRequest.Detail. Omit it and the answer is the index alone.
type ScanDBResponse ¶ added in v0.2.0
type ScanDBResponse struct {
Measured bool `json:"measured"`
Note string `json:"note,omitempty"`
Findings []findings.Finding `json:"findings"`
Surface []surfaceindex.Entry `json:"surface"`
Count int `json:"count"`
Withheld int `json:"withheld"`
WithheldNote string `json:"withheld_note,omitempty"`
Score int `json:"score"`
// Detail carries the full item for every requested id that matched, byte
// for byte the pre-change flat Surface shape.
Detail []findings.SurfaceItem `json:"detail,omitempty"`
// Unrecognized names every requested id that matched nothing. Naming it is
// the point: an empty success would say the item has nothing to declare,
// which is a different and false answer from "there is no such item".
Unrecognized []string `json:"unrecognized,omitempty"`
UnrecognizedNote string `json:"unrecognized_note,omitempty"`
// Bytes/IndexBytes/OverBudget/BudgetNote declare this response's own size,
// wired only once Detail exists (design D5) — the exact shape that made
// CoverageResponse under-declare its size before it carried Bytes/IndexBytes
// (#1664, closed in S3): bytes is measured LAST, over index + detail.
Bytes int `json:"bytes"`
IndexBytes int `json:"index_bytes"`
OverBudget bool `json:"over_budget,omitempty"`
BudgetNote string `json:"budget_note,omitempty"`
}
ScanDBResponse is the standalone DB-structure result. Measured distinguishes "audited" from "not audited": when false, Note says why (no schema_paths, no schema parser, or the sensor is disabled) and Findings/Surface are empty — it is NOT a "clean" result. Score is a plain dimension score (0-100), like codefit-scan-security; this tool does not compute by_dimension.
Surface is the LIGHT index (surfaceindex.Entry), always complete — nothing is withheld (Count/Withheld/WithheldNote, mirroring DBSection — design D1/D4). Detail carries the full findings.SurfaceItem for every requested id that matched; Unrecognized names every id that matched nothing, with a note (D3): codefit is stateless and cannot tell "never existed" from "the schema moved".
func HandleScanDB ¶ added in v0.2.0
func HandleScanDB(req ScanDBRequest) (ScanDBResponse, error)
HandleScanDB runs the DB sensor over the project and returns its structural findings + surface. A thin adapter: it resolves the provider, loads the config, and delegates to the DB sensor (which reads the schema, runs the core rules, and stamps identity). Standalone — it touches nothing in scan-all.
type ScanEndpointRequest ¶
type ScanEndpointRequest struct {
Root string `json:"root"`
Language string `json:"language"`
File string `json:"file"`
}
ScanEndpointRequest is the input to codefit-scan-endpoint: a project root, language, and the file (relative to root, as it appears in scan-all's File fields) to re-analyse on demand.
type ScanEndpointResponse ¶
type ScanEndpointResponse struct {
File string `json:"file"`
Found bool `json:"found"`
Endpoints []report.EndpointReport `json:"endpoints,omitempty"`
Note string `json:"note,omitempty"`
}
ScanEndpointResponse carries the full per-endpoint detail for one file: every handler in it with all its concerns (signals, reason_to_review, certainty, fact fields) — the same concern contract as scan-all. Found is false when the file has no auditable concerns (not a route handler, or nothing to enumerate).
func HandleScanEndpoint ¶
func HandleScanEndpoint(req ScanEndpointRequest) (ScanEndpointResponse, error)
HandleScanEndpoint re-analyses a single file on demand and returns its endpoints with full detail. STATELESS: it re-runs the static analysis over the project and filters to the requested file — it retrieves nothing stored. codefit does not keep the surface items waiting to be asked for; it recomputes the request. Static analysis is cheap, and re-running the same pipeline guarantees the detail here is identical to what scan-all would have shown for that endpoint (ADR 0008).
type ScanRequest ¶
type ScanRequest struct {
Root string `json:"root"`
Language string `json:"language"`
// ChangedFiles narrows the audit to these project-relative paths (layer 0 of
// the filtering pyramid). codefit does not ask git which files changed — it
// has no power over the user's git, and the calling agent already knows what
// it touched. Absent or empty means a FULL audit, never "audit nothing", and
// a narrowed run declares itself in the response's scope block.
ChangedFiles []string `json:"changed_files,omitempty"`
}
ScanRequest is the input to codefit-scan-security: a project root, a language, and optionally the files to narrow the audit to.
type ScanResponse ¶
type ScanResponse struct {
Findings []findings.Finding `json:"findings"`
Surface []findings.SurfaceItem `json:"surface"`
Score int `json:"score"`
Blocked bool `json:"blocked"`
Scope ScopeBlock `json:"scope"`
// SurfaceCoverage declares which of surface.ProviderCategories this
// language's provider mapped and which it did not (R2,
// docs/specs/declared-partial-language-exposure.md): a project whose
// provider maps a narrow slice of the vocabulary (Go today: authz only)
// must never report surface_items with no statement that the rest was
// never searched for. ALWAYS present — HandleScanSecurity errors before
// reaching this point when no provider resolves, so a returned response
// always has one.
SurfaceCoverage surface.CoverageStatement `json:"surface_coverage"`
}
ScanResponse is the deterministic + surface result over the project (the §11 contract): flat findings and surface, the dimension score, and whether the project is blocked (an unconsented critical security finding). Scope declares how much of the project this result describes. It is ALWAYS present, so `blocked: false` is never read as a wider claim than it is.
func HandleScanSecurity ¶
func HandleScanSecurity(req ScanRequest) (ScanResponse, error)
HandleScanSecurity runs the security sensor over the project and returns the flat findings + surface. A thin adapter: it resolves the provider, runs the sensor (the deterministic rules + the surface queries, already wired there), and returns its result — no detection logic lives here.
type ScopeBlock ¶ added in v0.2.7
type ScopeBlock struct {
Mode string `json:"mode"`
Requested int `json:"requested"`
Audited int `json:"audited"`
AuditableTotal int `json:"auditable_total"`
Unmatched []string `json:"unmatched,omitempty"`
Note string `json:"note"`
}
ScopeBlock is a scan's account of HOW MUCH of the project it looked at. It exists because a partial audit that is indistinguishable from a full one is a lying auditor: the whole point of narrowing is that the narrowing stays visible.
- Requested is how many distinct files the caller asked for (canonical, so a path spelled twice counts once); 0 under a full scan.
- Audited is how many of them the pass actually examined, and AuditableTotal how many it COULD have — the denominator is the whole project, never the scope, or "3 of 412" collapses into a self-flattering "3 of 3".
- Unmatched are the requested paths the audit never reached: deleted, wrong extension, outside the project, inside a skipped directory. Without it an agent that passes three wrong paths gets "0 findings" and reads it as clean. Requested always equals Audited + len(Unmatched).
- Note is MANDATORY when partial and FORBIDDEN when full (see Validate).
func (ScopeBlock) Validate ¶ added in v0.2.7
func (s ScopeBlock) Validate() error
Validate enforces the honesty invariant in BOTH directions: a partial scan must declare itself in prose, and a full scan must not carry a caveat it has no basis for. Handlers call it before returning, so a violation is a loud error rather than an unlabelled partial result an agent would read as a full one — the failure this whole block exists to prevent.
type SecuritySection ¶ added in v0.2.8
type SecuritySection struct {
Measured bool `json:"measured"`
Note string `json:"note,omitempty"`
// SurfaceCoverage declares which of surface.ProviderCategories this
// language's provider mapped and which it did not (R2,
// docs/specs/declared-partial-language-exposure.md) — the "1 of 4"
// statement a bare `surface_items: 1` never made. Present only when
// Measured is true: a pointer, so a DB-only pass (Measured=false, no
// provider resolved) serializes no surface_coverage key at all, rather
// than a zero-valued statement that could be misread as "nothing
// unmapped".
SurfaceCoverage *surface.CoverageStatement `json:"surface_coverage,omitempty"`
}
SecuritySection reports whether the security dimension ran this pass (D3b). Measured=false is SOFT, exactly like DBSection: it never fails scan-all, it is reported. Note is empty when Measured is true (nothing to caveat) and non-empty otherwise, naming why (no provider resolved for the language) and what that means for the rest of the response (the schema may still have been audited; the code was not).
type SecuritySummary ¶ added in v0.2.9
type SecuritySummary struct {
Endpoints int `json:"endpoints"`
DeterministicFindings int `json:"deterministic_findings"`
SurfaceItems int `json:"surface_items"`
CertainConcerns int `json:"certain_concerns"`
}
SecuritySummary is the security dimension's own counts — verbatim the four fields the flat ScanAllSummary carried, so a consumer migrating reads summary.security.* for exactly the values it used to read at summary.*.
type SummaryTotals ¶ added in v0.2.9
type SummaryTotals struct {
DeterministicFindings int `json:"deterministic_findings"`
SurfaceItems int `json:"surface_items"`
}
SummaryTotals is the cross-dimension roll-up, over commensurable units only. It is a VALUE, not a pointer: totals are always computable (the nothing-measurable guard already refused the response where no dimension ran), and zeros here are backed by at least one non-null sub-block saying who was counted.
type SurfaceIDORRequest ¶
type SurfaceIDORRequest struct {
Files []FileInput `json:"files"`
}
SurfaceIDORRequest is the input to codefit-surface-idor.
type SurfaceResponse ¶
type SurfaceResponse struct {
Surface []findings.SurfaceItem `json:"surface"`
// HelperScope is set ONLY by HandleSurfaceAuthz and HandleSurfaceIDOR: it
// declares that their helper-dependent structural facts (known_authz_detected)
// were computed against codefit's BUILT-IN helper set, never the project's
// registered ones (ADR 0013). Like findings.SurfaceItem.StructuralFacts, this
// is a FACT about what was computed, never a judgment about the result — a
// false structural fact still means "not seen", not "unauthorized".
// omitempty keeps it entirely ABSENT (not present-and-empty) on
// HandleSurfaceOverfetch/HandleSurfaceNPlus1, whose facts never consult the
// helper set: "no mention" and "nothing to mention" must not be the same
// bytes.
HelperScope string `json:"helper_scope,omitempty"`
}
SurfaceResponse is the §11 surface contract: the items the agent must reason.
func HandleSurfaceAuthz ¶
func HandleSurfaceAuthz(req SurfaceIDORRequest) (SurfaceResponse, error)
HandleSurfaceAuthz enumerates the broken-authorization surface across the given files. It orders the items so the actionable ones — where no known authz helper was detected — come FIRST, the rest after (where a check exists, the agent verifies sufficiency). It does NOT reduce the list: the complete enumeration is preserved, only reordered, so findings surface instead of being buried in volume. Ordering by a fact is not a severity judgment — codefit does not say "no check is worse", it just surfaces the fact for the agent.
func HandleSurfaceIDOR ¶
func HandleSurfaceIDOR(req SurfaceIDORRequest) (SurfaceResponse, error)
HandleSurfaceIDOR enumerates the IDOR surface across the given files and returns it in the canonical JSON contract. Files that are not handled by a provider (or carry no IDOR surface) contribute nothing.
func HandleSurfaceNPlus1 ¶ added in v0.2.2
func HandleSurfaceNPlus1(req SurfaceIDORRequest) (SurfaceResponse, error)
HandleSurfaceNPlus1 enumerates the N+1 (DB-201) surface and orders by STRUCTURAL CERTAINTY, reusing the queryable facts — it does NOT filter (ADR 0005): a loop over three literal elements is enumerated exactly like a loop over an unbounded query result.
1st local + sequential await — the worst, most certain shape
2nd local + Promise.all-wrapped (concurrent) — still N queries
3rd frontier (a service/repository call — codefit cannot see the query;
kept last, honest, never dropped)
func HandleSurfaceOverfetch ¶
func HandleSurfaceOverfetch(req SurfaceIDORRequest) (SurfaceResponse, error)
HandleSurfaceOverfetch enumerates the over-fetching surface and orders by STRUCTURAL CERTAINTY, reusing the queryable facts — it does NOT filter:
1st local find, no select (structurally confirmed over-fetch — actionable) 2nd local find, with select (limited) 3rd frontier (service — codefit cannot see the find; kept last, honest)
The complete enumeration is preserved; only the order changes, lowering the uncertain (frontier) without dropping them — the unchecked-first principle on the local/frontier axis (ADR 0005). Ordering by a fact is not severity: codefit ranks by what it can assert with most certainty, the agent judges.
type Tool ¶
type Tool string
Tool is the name of an MCP tool codefit exposes. All tools use the codefit- prefix; the codefit-surface-* family enumerates surface for the agent to reason about rather than detecting (PRD section 11).
const ( ToolScanSecurity Tool = "codefit-scan-security" ToolScanDB Tool = "codefit-scan-db" ToolCheckCVEs Tool = "codefit-check-cves" ToolCheckPractices Tool = "codefit-check-practices" ToolScanTests Tool = "codefit-scan-tests" ToolSurfaceIDOR Tool = "codefit-surface-idor" ToolSurfaceAuthz Tool = "codefit-surface-authz" ToolSurfaceOverfetch Tool = "codefit-surface-overfetch" ToolSurfaceNPlus1 Tool = "codefit-surface-nplus1" ToolConfirmSurface Tool = "codefit-confirm-surface" ToolReviewCode Tool = "codefit-review-code" ToolScanAll Tool = "codefit-scan-all" ToolScanEndpoint Tool = "codefit-scan-endpoint" ToolBaselineList Tool = "codefit-baseline-list" ToolBaselineAccept Tool = "codefit-baseline-accept" ToolBaselinePrune Tool = "codefit-baseline-prune" ToolBaselineRegisterAuthzHelper Tool = "codefit-baseline-register-authz-helper" ToolBaselineUnregisterAuthzHelper Tool = "codefit-baseline-unregister-authz-helper" ToolCoverage Tool = "codefit-coverage" )
type VulnerableDependency ¶ added in v0.1.4
type VulnerableDependency struct {
Name string `json:"name"`
Version string `json:"version"`
Ecosystem string `json:"ecosystem"`
Vulnerabilities []cve.Vulnerability `json:"vulnerabilities"`
}
VulnerableDependency is one dependency that OSV.dev reports as vulnerable, with every vulnerability affecting its exact installed version.