Skip to content

Adopting on an existing codebase

A baseline records the violations a codebase already has: the analyzer's, and what declscope shrink reports. Both then report only what is new.

declscope baseline ./...       # writes .declscope-baseline.yaml

The file is found by the same upward lookup as the config, so its presence is all it takes.

packages:
  github.com/you/app/database:
    boundary:
      userRepository:
        - normalizeEmail
      statement:
        - Statement.wheres
      (core):
        - open
    overexported:
      userRepository:
        - FindUser
Spelling
A package-level declaration normalizeEmail, under its file's namespace
A member Type.member, under the namespace of its type's file
A method Type.method, under the namespace of the file it is written in
The core namespace (core)

An entry is keyed by package, rule, namespace and declaration, never by position. It survives code moving within its file. It does not survive a declaration moving to another namespace, since the same use then crosses a different boundary.

A baseline is regenerated, never edited.

declscope baseline ./...
git diff .declscope-baseline.yaml   # the record of what was cleaned up

Tip

A regeneration records what one build configuration sees. A module that runs declscope under several GOFLAGS=-tags=... or GOOS settings keeps only the last run's entries.

Where the baseline is written
declscope baseline [-o path] [-config path] [-merge] [packages]   # packages default to ./...

Each package's entries go to the file the analyzer will consult for that package.

Target When
The baseline named by the package's nearest config file That config has a baseline key
The nearest existing .declscope-baseline.yaml above the package No config names one
A new .declscope-baseline.yaml in the working directory Neither of the above exists

For example, with a config in internal/billing/ naming its own baseline, and a baseline already in internal/legacy/:

.declscope-baseline.yaml               internal/user's entries
internal/
├── user/user.go
├── billing/
│   ├── .declscope.yaml                baseline: billing-baseline.yaml
│   ├── billing-baseline.yaml          internal/billing's entries
│   └── billing.go
└── legacy/
    ├── .declscope-baseline.yaml       internal/legacy's entries
    └── legacy.go
$ declscope baseline ./...
declscope: recorded 1 violation(s) in .declscope-baseline.yaml
declscope: recorded 1 violation(s) in internal/billing/billing-baseline.yaml
declscope: recorded 1 violation(s) in internal/legacy/.declscope-baseline.yaml

-o bypasses that lookup and gathers every entry into one file.

Recording shrink's reports loads the whole module once more. -shrink=false skips it, for a module that does not run shrink.

If no pattern matches a package, the run stops and writes nothing, -o included. A pattern that matches nothing beside others that do gets a warning, as it does from go vet.

Every file written is regenerated wholesale. The existing one is never read, so a baseline that fails to parse is replaced like any other. A narrower pattern therefore drops the entries of every package it does not load from the files it writes:

$ declscope baseline ./...           # records a and b
$ declscope baseline ./a             # the file now holds only a
$ declscope baseline -merge ./a      # a is regenerated, b is kept

-merge reads each file it writes first. It replaces the entries of the packages this run loaded, their test variants included, and keeps every other entry. Under -shrink=false it keeps shrink's entries too, since the run recorded none. It also keeps them for a package shrink did not judge, which the run names on stderr as shrink does. A file that -merge cannot parse stops the run, and nothing is written.

Note

-merge also keeps the entries of a package that was deleted or renamed, since no pattern loads it any more. A run over ./... without -merge prunes them.

Warning

Some packages cannot reach the working directory by that lookup, such as one in another module. The run then refuses and names them, rather than recording entries nothing would find.

Tip

skills/declscope-adoption is a skill for an AI agent doing this work. It covers what each diagnostic shape means, and the measurement traps that produce false confidence. Its companion, skills/declscope-authoring, is for everyday code: where a declaration goes, and how to name it accurately. The binary carries both:

declscope skill install            # the agents already set up in this project
declscope skill install --agent claude-code --scope user
declscope skill list               # where they are, and whether they are current

Without the binary, gh skill install mpyw/declscope declscope-authoring --agent claude-code writes to the same directories, and the same with declscope-adoption. The binary's installer is go-skill-embed, which takes them from gh skill install.