netdisco
CLAUDE.md936 starsChanged 10 months agoBSD-3-ClauseView on GitHub
Netdisco is a web-based network management tool (Perl + a little Python). It discovers devices via SNMP/CLI/APIs, stores IP/MAC/topology data in PostgreSQL, and exposes a web UI and CLI. Three runtime pieces share this one codebase: All three load configuration and DB schema from the same lib/App/Netdisco/DB.pm / App::Netdisco::Environment. This is a Module::Build-based Perl distribution (Build.PL, not Makefile.PL). There is no local localenv/Build in a fresh checkout — those come from the netdisco Docker dev/build image, which is what CI…
We split this CLAUDE.md into the rules it gives an agent
4 rules. About 0 are common, 1 is distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 13
All three load configuration and DB schema from the same lib/App/Netdisco/DB.pm / App::Netdisco::Environment.
- Very specific: line 36
Always name the specific .t / .test.js file rather than globbing — node --test exits 0 on a glob that matches nothing, so a moved/renamed …
- Distinctive: line 48
When a test's history matters (e.g.
- Very specific: line 81
Don't touch share/config.yml defaults casually.
≈ “Common” is an estimate: at least 1 in 5 of the 25,680 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this is
Netdisco is a web-based network management tool (Perl + a little Python). It discovers devices via SNMP/CLI/APIs, stores IP/MAC/topology data in PostgreSQL, and exposes a web UI and CLI. Three runtime pieces share this one codebase:
- Web frontend — Dancer (v1, classic — not Dancer2) app under
lib/App/Netdisco/Web/, Template Toolkit views inshare/views/. - Backend daemon — a job queue/worker system (
lib/App/Netdisco/Backend/,lib/App/Netdisco/Worker/) that runs discovery/collection jobs against devices. - CLI — scripts in
bin/(e.g.netdisco-do) for ad hoc job runs and admin tasks.
All three load configuration and DB schema from the same lib/App/Netdisco/DB.pm / App::Netdisco::Environment.
Commands
This is a Module::Build-based Perl distribution (Build.PL, not Makefile.PL). There is no local localenv/Build in a fresh checkout — those come from the netdisco Docker dev/build image, which is what CI uses.
perl Build.PL # generate the Build script
./Build test # run default test suite
./Build test --test_files xt/ # run the full xt/ test suite (what CI runs)
./Build test --test_files xt/NN-name.t # run a single Perl test fileFrontend JS tests use Node's built-in test runner (Node 22), not Jest — there is no test config file, no npm test, and no package.json scripts (package.json there is just a manifest of vendored frontend libs, not a build step):
node --test xt/js/portsort.test.js
node --test xt/js/view-templates.test.js
node --test xt/js/netmap-loading.test.js
node --test xt/js/netmap-legend.test.js
node --test xt/js/netmap-autosave.test.jsAlways name the specific .t / .test.js file rather than globbing — node --test exits 0 on a glob that matches nothing, so a moved/renamed test file can silently stop running (see comments in .github/workflows/frontend-tests.yml).
CI (.github/workflows/build-and-publish.yml) runs inside the netdisco/netdisco:latest-backend container image and invokes tests via localenv perl ./Build.PL then localenv ./Build test --test_files xt/; there's a separate .github/workflows/frontend-tests.yml for the Node tests above.
Test layout (xt/)
xt/*.t— Perl tests, numbered by area (10-sort_port.t,20-checkacl.t,40-template-snapshots.t, etc). Many are narrow regression guards for a specific past incident — read the comment block at the top of a test before changing it; they often explain why the check is scoped the way it is (see e.g.xt/39-html-escaper.t).xt/lib/— test-only Perl support code, including fake worker plugins underApp::NetdiscoX::Worker::Plugin::*used to exercise the plugin loader.xt/js/— Nodenode:testfiles covering vendored/hand-written frontend JS inshare/public/javascripts/.xt/snapshots/— golden HTML snapshots of Template Toolkit views, checked byxt/40-template-snapshots.t. Regenerate viaxt/bin/regenerate-snapshotswhen a template change is intentional.xt/bin/regenerate-portsort-corpus— regenerates the port-name sorting test corpus.
When a test's history matters (e.g. it says "ported from a QUnit page removed in commit X"), check that commit before inventing new cases — it's often the actual spec.
Architecture
Config and environment
share/config.ymlships the defaults and is never edited directly (it says so at the top) — deployment overrides go in~/environments/deployment.yml(a Netdisco home dir, not this repo).lib/App/Netdisco/Environment.pmandlib/App/Netdisco/Configuration.pmresolveNETDISCO_HOMEand layer config files.lib/App/Netdisco/DB.pmis theDBIx::Class::Schemaroot. Schema migrations are plain numbered SQL files inshare/schema_versions/App-Netdisco-DB-<from>-<to>-PostgreSQL.sql, applied viaApp::Netdisco::DB::SchemaVersioned.
Worker/job plugin system (backend)
This is the part most non-web feature work touches. Jobs (App::Netdisco::Backend::Job) have an action (e.g. discover, macsuck, arpnip). App::Netdisco::Worker::Loader::load_workers loads every configured worker plugin whose package matches ::Plugin::<action> (or ::Plugin::Internal), from worker_plugins / extra_worker_plugins in config; an X::Foo name maps to App::NetdiscoX::Worker::Plugin::Foo for site-local plugins outside this dist.
Plugins register via register_worker (lib/App/Netdisco/Worker/Plugin.pm), declaring a phase (check / early / main / user / store / late) and a priority (drivers get priority from driver_priority config). App::Netdisco::Worker::Runner drives a job through the phases in order, handling per-device auth (device_auth config, filtered/reordered per device via ACLs and device_auth_tag_hint) and Python worklets (enable_python_worklets → App::Netdisco::Worker::Plugin::PythonShim → App::Netdisco::Transport::Python, which shells out to a long-lived Python subprocess; worklets live in share/python/).
Concrete action plugins live in lib/App/Netdisco/Worker/Plugin/<Action>.pm (and a same-named subdirectory for ::namespace sub-plugins, e.g. Vlan/, Nbtstat/). SNMP/device-model logic itself is largely SNMP::Info (external CPAN dep), not reimplemented here.
Web app
lib/App/Netdisco/Web.pm is the Dancer app entry point; it monkeypatches a few core Dancer subs (redirect, error rendering, uri_for, path) early to add multi-tenant (/t/<tenant>) URL support and JSON-shaped errors for API requests — check there first if routing/redirect behavior looks unexpected.
Feature areas register themselves as web plugins (web_plugins in config, e.g. Report::PortVLANMismatch, AdminTask::DuplicateDevices) through lib/App/Netdisco/Web/Plugin.pm, which exposes register_template_path, plus shared state (_navbar_items, _search_tabs, _device_tabs, _admin_tasks, _reports, etc) that plugins push into to extend the UI without editing core routes. Concrete plugins live under lib/App/Netdisco/Web/Plugin/{Report,AdminTask,Device,Search}/.
Views are Template Toolkit under share/views/{layouts,ajax,sidebar}/..., mirroring the same device/report/search/admintask groupings. Pages are loaded with htmx (see recent history) rather than full client-side routing. Frontend JS is vendored/hand-rolled under share/public/javascripts/ (see package.json for the vendored library manifest — it's documentation only, nothing installs from it) — always check xt/js/ for existing coverage before changing a .js file there.
DB layer
lib/App/Netdisco/DB/Result/*.pm are DBIx::Class result classes (one per table, e.g. Device.pm, DevicePort.pm, Node.pm); lib/App/Netdisco/DB/ResultSet/*.pm hold custom query methods. lib/App/Netdisco/GenericDB/ is a separate, more dynamic schema layer used for user-defined/"generic" reports against arbitrary external databases (external_databases / tenant_databases config), distinct from the main app schema.
Working conventions seen in this repo
- Perl style:
use strict; use warnings;everywhere, heavy use ofMoo/Moo::Rolefor new classes,Try::Tinyover nativeeval,namespace::cleanafterMoo. - Comments in this codebase tend to record why, especially in tests and monkeypatches (a past incident, a constraint that isn't obvious from the code) — match that style rather than describing what the code does.
- Don't touch
share/config.ymldefaults casually; it's shipped config documented on the wiki (linked at the top of the file), and every key needs a corresponding default there even if real values come fromdeployment.yml.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

