Skip to content

Development and Contributing

heymaikol edited this page Aug 18, 2026 · 2 revisions

Development and Contributing

Bug reports, focused pull requests, and platform testing are all welcome. Participation is governed by the Code of Conduct.

CONTRIBUTING.md is authoritative. It ships in the source tree and in tagged releases, so it is correct for whatever version you actually checked out. This page is the orientation around it.

Before opening an issue

  • Search existing issues first.
  • Include the output of netdoc --version.
  • Include your OS, architecture, install method, target form, and the smallest sequence that reproduces the problem.
  • A netdoc --json run is usually the single most useful attachment, and failed_stage alone often routes the report.

Review what you paste. Reports and command output may contain internal hostnames, IP addresses, usernames, and interface names.

Suspected vulnerabilities go through a GitHub Security Advisory or the contact in SECURITY.md, not a public issue.

Setting up

Network Doctor requires the Go version declared in go.mod. There is nothing else to install: every tool in the validation gate runs through go run at the version CI uses.

git clone https://github.com/heymaikol/network-doctor.git
cd network-doctor
go test ./...
go run . github.com

To work on the simulator, build both binaries so a run grades the netdoc you just changed (see Which netdoc gets run):

CGO_ENABLED=0 go build -o netdoc .
CGO_ENABLED=0 go build -o netdoc-sim ./cmd/netdoc-sim
./netdoc-sim run broken-dns

Where code goes

Package Owns
main.go CLI arguments, process I/O, application startup
internal/diagnostic Target parsing, native probes, per-OS route/SSID lookups, verdict logic, with no terminal presentation
internal/ui Bubble Tea state, rendering, tool jobs
internal/report The stable JSON report shape
internal/textsafe Sanitises untrusted remote and subprocess text
internal/simulation + cmd/netdoc-sim Virtual networks for testing the engine, and nothing here ships in netdoc

The rule: network semantics go in diagnostic, interaction and rendering go in ui. The UI depends on diagnostics; diagnostics never depend on the UI. The simulator depends on diagnostic for probe IDs and target parsing, and on nothing in ui.

This is enforced by architecture_test.go, not just documented; see Architecture.

Other standing rules:

  • Put OS-specific behaviour in build-tagged or platform-suffixed files.
  • Keep probes unprivileged and time-bounded.
  • Pass commands as argument slices, never shell strings.
  • Never interpolate a target into a shell.
  • No automatic privilege escalation, and no rewriting the user's config.
  • Release builds are CGO_ENABLED=0, so do not add cgo dependencies.

The validation gate

Run the complete gate before submitting. The authoritative, copy-pasteable list is the README's Tests section: it pins exact tool versions, and it is what the gate skill and CI both point at. Do not work from a copy.

What it covers, and why each part is there:

Stage Why
go vet, build, go test ./... The baseline.
-tags integration Real-socket tests. Loopback only.
-tags netns_integration Real Linux namespaces. Rootless; skips itself where unavailable.
-race (plain and integration) The probe scheduler is concurrent by design.
Fuzz: sanitizer, target parser, encrypted-DNS response verifier These are the three parsers that see hostile input.
golangci-lint, govulncheck, goreleaser check Lint, known vulnerabilities, release config.

Two flags in that gate look redundant and are not: the namespace suite keeps -v because a skipped run and a real one both print just ok otherwise, and -count=1 because a cached result would not have exercised any namespace at all.

Race, fuzz, and namespace checks run only on Linux in CI.

Additional checks, by what you touched

If you changed… Also run
A build-tagged or _linux/_darwin/_windows file GOOS=darwin go build ./... and GOOS=windows go build ./...
internal/textsafe The FuzzSanitize target
Target parsing or the encrypted-DNS response verifier The corresponding fuzz target
The Dockerfile or the image's release job Build the image and run the container tagged tests (needs Docker or Podman, which is why it is not in the base gate)
A fixed probe endpoint in internal/diagnostic netdoc-sim run healthy, the canary
A man page's documented behaviour Update its .TH date to that change's date

Test hygiene: ordinary tests stay deterministic, rootless, and offline. Real-socket tests keep the integration tag and loopback-only scope. Real namespace tests keep the netns_integration tag.

Cleaning build output

Local builds and release dry runs leave generated files in the clone:

rm -rf netdoc netdoc-sim network-doctor dist vendor network-doctor-*-vendor.tar.gz

vendor/ matters most. GoReleaser's before-hooks write it and the matching tarball into the repo root at release time, and a leftover vendor/ silently switches every later go build and go test in the clone to -mod=vendor, resolving dependencies from that snapshot instead of go.mod. A stale dist/ also aborts the next GoReleaser run, which requires it empty.

Prefer that explicit list over git clean -Xdf: everything above is ignored, but so are editor and tool settings, which git clean would delete too.

Pull requests

Keep each pull request focused on one behaviour. One behaviour per commit, too.

A good PR description states:

  • the user-visible effect;
  • the validation commands you actually ran;
  • any platform-specific behaviour, and explicitly which OSes you did not test;
  • a linked issue where there is one.

For TUI layout changes, include a screenshot or terminal capture.

Commit subjects are short, simple, and imperative. The project deliberately does not use Conventional Commit type or scope prefixes, so Add Star History chart, never docs: add star history chart.

Adding a scenario

The workflow, in short:

  1. Copy the closest YAML under internal/simulation/scenarios/ and change only the topology, fault, test, and expectation the behaviour needs.
  2. Validate the file by path. If it is built in, rebuild and validate its filename stem through the embedded library too.
  3. Run it with -dry-run, then normally on a supported Linux host. Use -json to inspect structured evidence rather than matching report prose.
  4. Add the smallest unit test for new parsing, scheduling, comparison, or evidence logic. Add a focused netns_integration test only when the behaviour must be proved through real namespaces.
  5. If diagnostic endpoints changed, update every affected alias and zone, then run the healthy canary.

Remember the maintenance scope: a fault model or scenario is added for a real bug, a diagnostic blind spot, a reproducible field condition, a regression, or an identified missing network behaviour, not for general simulator expansion.

The full authoring reference, including fault semantics and the expectation schema, is in docs/simulation.md.

Documentation changes

Before writing documentation, check Documentation Map. The short version: if correctness depends on the documentation changing in the same commit as the code, it belongs in the repository, and several surfaces are tested for exact agreement with the real binary, so a flag added without updating the man page and all six completion files will fail the build.

A documentation-only change does not require running the namespace integration tests unless it exposes a reason to verify namespace behaviour.

Where next

Clone this wiki locally