@cryptotaxi247 / netdata-1 / commits / 5d611c4ce

agents: project-writing-collectors skill + SOW lifecycle updates (#22386)

* init: add SOW (Statement of Work) system Bootstrap a local SOW system for tracking non-trivial work: - pending/current/done/specs directories with .gitkeep - SOW.template.md with format guide - audit.sh helper script - update .gitignore and AGENTS.md with SOW references * updated sow completion * agents: add project-writing-collectors skill (first draft) Routing-and-pointers oriented draft for the project-writing-collectors runtime skill plus its SOW (SOW-0001). Indexed in AGENTS.md. This draft is intentionally preserved as a baseline before a structural rewrite that re-centers the skill on mental model, best practices, and data-type-driven thinking. * agents: restructure project-writing-collectors skill Re-centers the skill on mental model, framework-agnostic best practices, and data-type/domain-driven thinking, with the plugin landscape and canonical pointers demoted to reference. Adds: - research-online and cross-project spec-comparison discipline - testing-by-application-class (open-source vs closed-source vs hardware vs protocol) - dashboard-shaping section covering NIDL, SNMP profiles, statsd synthetic_charts, OTEL per-metric YAML mappings, and the deterministic Prometheus exposition mapping - obsoletion as a truthfulness principle (sibling to gaps-are-data), separate from cardinality bounding - cardinality bounding directives: max_* + selectors mandatory, and the three upstream-data-shape sub-cases ("Other" bucket / upstream cherry-pick / aggregations by grouping key) - per-data-type chapter (metrics, logs, live snapshots, topology, netipc enrichment) - per-domain common practices (DBs + query Functions, network/SNMP + topology Functions, containers + netipc enrichment, web servers + access-log Functions, flow protocols) * sow: close SOW-0001 (project-writing-collectors skill) Status: in-progress → completed; file moved to .agents/sow/done/. Validation, Outcome, Lessons Extracted, and Followup gates filled out based on what shipped in the prior two commits. Lessons captured: keep comparative claims about Netdata out of in-repo skills; obsoletion is a truthfulness concern, not a cardinality one; max_* and selectors must be coupled; the SOW close should land with the work as one commit (this SOW required a separate close commit by user approval after the work had already been pushed).

Costa Tsaousis committed May 2, 2026 at 23:16 UTC 5d611c4ce8c23560f8b392d110b9121e028de026
5 files changed +1173 -2
.agents/skills/project-writing-collectors/SKILL.md new
+516
@@ -0,0 +1,516 @@
1 +---
2 +name: project-writing-collectors
3 +description: Best practices and orientation for AI assistants authoring or modifying Netdata data-collection plugins or modules in any language. Read before adding a new collector, modifying an existing one, working on logs, topology, NetFlow/sFlow/IPFIX, OTEL ingestion, SNMP profiles, statsd, Prometheus scraping, or interactive Functions. Covers the mental model, framework-agnostic best practices, dashboard-shaping mechanisms (NIDL, SNMP profiles, statsd synthetic_charts, OTEL mappings, Prometheus exposition), production quality criteria, the plugin landscape, per-data-type patterns (metrics, logs, snapshots, topology, enrichment), per-domain common practices, and a pre-PR self-check.
4 +type: project
5 +---
6 +
7 +# Writing Netdata data collection plugins and modules
8 +
9 +## What this skill is
10 +
11 +You are about to add or modify data collection in the Netdata Agent. This skill is a manifesto and a routing map. It tells you the mindset to apply, the principles you cannot violate, the ways the dashboard gets shaped from upstream data, the quality bar that separates a draft from a shippable collector, and where to look for depth. It is not a tutorial — the deep references already exist in the repo. Your job is to know they exist, pick the right one, and produce work that blends with the patterns the maintainers already accept.
12 +
13 +The skill is organized as: mental model → best practices → dashboard shaping → quality bar → environment reference → applied per data type → applied per domain. Read top to bottom on your first pass; come back to specific sections as the task narrows.
14 +
15 +## 1. Mental model
16 +
17 +How to think about Netdata data collection. Internalize this before designing anything.
18 +
19 +### 1.1 Frequent collection at scale
20 +
21 +The Agent ships on >1.5M new daily installs across physical servers, VMs, containers, IoT devices, embedded systems, and exotic Unixes. Default collection is 1-second; many collectors raise it (`ping` 5s, SNMP 10s) when the source warrants it. Anything you do inside the collection cycle — allocate, log, reconnect, retry, parse, format — is multiplied by that population. Hot-path discipline is the entry ticket, not an optimization.
22 +
23 +### 1.2 Metric structure is dashboard UX
24 +
25 +How dimensions group into charts and how labels attach to instances *is* the dashboard the user sees. Mirroring upstream data structures one-to-one produces a chart per metric, which is unusable. **NIDL** — Nodes, Instances, Dimensions, Labels — is the model. Every dashboard-shaping mechanism (§3) feeds into it.
26 +
27 +### 1.3 IDs are public contracts
28 +
29 +Chart `context`, chart IDs, dimension IDs, instance labels — once shipped, they bind health alerts, dashboards, exports, anomaly detection, ML jobs, streaming consumers, and Netdata Cloud. Renaming silently breaks all of them. Treat them as permanent.
30 +
31 +### 1.4 Gaps are data
32 +
33 +When you cannot measure a value this iteration, emit nothing for that dimension. The dashboard renders the gap; the user knows collection is broken. Defaulting to `0` fabricates a working state and hides the bug. Past pain in `src/collectors/proc.plugin/proc_net_dev.c` (search `shouldn't use 0 value, but NULL`).
34 +
35 +### 1.5 Obsolete what's gone
36 +
37 +When the collector knows an entity has gone away — a process exited, a container was removed, a profile target was dropped, a network interface disappeared, a managed device went offline — mark its chart obsolete. The dashboard then renders it as historical, not as actively collected; alerts stop binding to it; streaming and ML stop costing for it.
38 +
39 +This is a truthfulness principle, not a cardinality one. It applies at any cardinality, including a single instance. Without obsoletion, the chart looks alive on the dashboard, alerts may continue evaluating against frozen data, and the user is misled about what is and isn't being collected.
40 +
41 +Mechanics:
42 +- C: `rrdset_is_obsolete___safe_from_collector_thread()` in `src/database/rrdset.c:116` flags `RRDSET_FLAG_OBSOLETE`. Reverse with `rrdset_isnot_obsolete()` (line 140) when the entity reappears.
43 +- go.d: `c.Obsolete = true` on the chart struct; the framework appends `obsolete` to the CHART command. Documented at `src/go/BEST-PRACTICES.md:94-108`.
44 +- Anti-flip-flop: if an entity may disappear and reappear quickly, wait roughly 1 minute of absence before obsoleting. Thrashing charts hurt streaming and ML.
45 +
46 +### 1.6 Your knowledge is stale — research the current spec
47 +
48 +Specs, vendor protocols, RFCs, and SDK behavior move. Before you design a collector or interpret a payload:
49 +
50 +- Read the **current** spec from the official source (RFC, vendor portal, SDK docs).
51 +- For application/database/protocol collectors, read the **current** application's release notes — fields, defaults, and semantics shift between versions.
52 +- Do not trust your prior-knowledge interpretation of a binary format, OID semantics, or HTTP/JSON shape. Verify against an authoritative document or live behavior.
53 +
54 +Prior-knowledge mistakes that recur: confused field names in NetFlow v5 vs v9 vs IPFIX, wrong endianness on a vendor MIB, outdated PostgreSQL `pg_stat_*` columns, deprecated Kubernetes API resources.
55 +
56 +### 1.7 When the spec is ambiguous, look at how others solved it
57 +
58 +Specs leave many decisions implementation-defined. Vendor implementations bend specs in well-known ways. When you face an interpretation dilemma:
59 +
60 +- Read 2–3 popular open-source monitoring tools that already collect this data — Prometheus exporters, Zabbix templates, Datadog Agent integrations, ntopng (network protocols), librenms / OpenNMS / Akvorado (SNMP and flow), collectd (system data), pmacct / nfdump (flow protocols).
61 +- Compare their parsers, field interpretation, and edge-case handling.
62 +- Their code encodes real-world device quirks the spec doesn't document.
63 +- Cross-check against the upstream protocol's reference implementation when one exists.
64 +
65 +This is how you avoid shipping a parser that fails on the first real device. If you have a local mirror of monitoring projects, use it; otherwise clone the relevant upstreams to `/tmp/` and read their source.
66 +
67 +### 1.8 Mirror an existing Netdata collector
68 +
69 +The repo holds 132 go.d modules and 24 internal C plugins. Maintainer patterns live there, not in any prose doc. After you've reality-checked the upstream protocol, pick the closest existing Netdata collector by domain and mirror its structure. Caveat: only 5 go.d modules use V2 — see §5.3.
70 +
71 +### 1.9 Remote-monitored systems are vnodes
72 +
73 +When one collector talks to N targets (SNMP devices, remote DBs, cloud APIs, IPMI hosts, vCenter clusters), each target is a **vnode** so its metrics, alerts, and RBAC behave as if it were a separate node in Netdata Cloud. Every remote-target collector wires vnodes from the start.
74 +
75 +### 1.10 Cardinality discipline
76 +
77 +- A chart with thousands of dimensions, or an instance list with thousands of entries, is unusable on the dashboard. The user cannot read it.
78 +- A collector that emits potentially thousands of instances per monitored application is operationally wasteful — the data carries no insight. It pollutes streaming, ML, alerts, and queries for no benefit.
79 +- A series is paid for across multiple subsystems: dbengine storage, agent memory, streaming bandwidth (per hop, including Netdata Cloud), ML training (one model per series), alert evaluation, dashboard render. None of these costs is large in isolation; together they justify ending up with what the user actually wants to see.
80 +
81 +Design for usefulness, not raw count. Bound cardinality (§2.5), and never ship "one chart per request / per PID / per ephemeral connection" without bounds.
82 +
83 +### 1.11 Layered configuration
84 +
85 +Per-job source priority: `stock < discovered < user < dyncfg`, matched by job identity. A higher-priority source replaces a lower-priority job with the same identity; non-colliding jobs continue to load. IaC users configure via files in `/etc/netdata`; dashboard users configure via DYNCFG; both paths must work for the same collector.
86 +
87 +## 2. Best practices
88 +
89 +Framework-agnostic, ordered by impact.
90 +
91 +### 2.1 Test against reality
92 +
93 +Source test data based on what you're collecting:
94 +
95 +- **Open-source / freely available applications** (MySQL, PostgreSQL, NGINX, Redis, MongoDB, RabbitMQ): run the actual application locally (Docker, native install). Validate against real output. Cover multiple versions when defaults diverge.
96 +- **Closed-source / vendor / SaaS** (vendor switches, IBM workloads, cloud APIs, hypervisors): harvest fixtures from other open-source monitoring projects — Prometheus exporters, Zabbix templates, Datadog Agent integrations, vendor SDK samples, anonymized traces in vendor PRs/issues. Their fixtures are the most complete "real-world" dataset publicly available.
97 +- **Hardware-dependent** (network gear, IPMI, PCIe sensors): capture pcaps from real devices when accessible; otherwise vendor SDK samples, public packet captures, fixtures from pmacct / nfdump / ntopng (for flow protocols).
98 +- **Protocol parsing** (NetFlow / sFlow / IPFIX / OTEL / SNMP): vendor SDK samples, public dumps, fuzz-test corpora. NetFlow keeps fixtures under `src/crates/netflow-plugin/testdata/flows/` with sourcing recorded in `testdata/ATTRIBUTION.md` — do the same for any new fixtures with redistribution-sensitive provenance.
99 +
100 +Don't fabricate test data the parser passes by accident. Don't skip tests "because this protocol can't be tested locally" — that's exactly when fixtures matter most. Standard go.d test-function names: `Test_testDataIsValid`, `TestCollector_ConfigurationSerialize`, `TestCollector_Init`, `TestCollector_Check`, `TestCollector_Collect` — match the convention in adjacent collectors. Functions get a dedicated validator at `src/go/tools/functions-validation/` (E2E plus schema checks).
101 +
102 +### 2.2 Hot-path discipline
103 +
104 +`Collect()` runs every `update_every` seconds. It must:
105 +
106 +- Allocate buffers, maps, slices, parsed regexes once at `Init()` and reuse them. Reset at the top of `Collect()` if needed; see `ping/collect.go` for a V2 reference.
107 +- Hold persistent connections; reconnect only on failure with backoff.
108 +- Cache anything stable between iterations: schema, capabilities, profile selections.
109 +- Finish well under one cycle even on a slow target.
110 +
111 +Anti-pattern (search and avoid): `mx := make(map[string]int64)` per `Collect()` (e.g., `src/go/plugin/go.d/collector/ap/collect.go`). Don't allocate fresh structures per cycle. Don't reconnect every cycle.
112 +
113 +### 2.3 Error handling
114 +
115 +Every error log answers three questions: **what operation, what target, what was expected vs observed**. Wrap errors with context (Go: `fmt.Errorf("...: %w", err)`); preserve the cause; check return codes from system calls and library functions.
116 +
117 +Don't return a bare `err` with no context. Don't log `"failed"`. Don't ignore syscall returns or library NULLs.
118 +
119 +### 2.4 Logging discipline
120 +
121 +- `debug` inside the collection loop.
122 +- `warn` or `error` once per known-recoverable condition, gated by an internal flag — never per cycle.
123 +- `info` / `notice` for once-at-startup events.
124 +- Reserve `error` severity for operator-actionable issues; transient conditions are `warn`.
125 +
126 +Past pain: an `ebpf.plugin` regression flooded logs because the collection loop logged every PID allocation. Per-cycle logs are forbidden.
127 +
128 +### 2.5 Cardinality bounding
129 +
130 +When a collector emits one chart per discovered entity (process, connection, profile target, container, schema, queue, route), bound the count and let the operator scope it. (Obsoletion of entities the collector knows have gone is a separate concern — see §1.5.)
131 +
132 +**`max_*` is mandatory for entities that may grow without bounds.** Without a cap, a single misbehaving target (a runaway log rotator, a container churn loop, a vendor-specific deep table) can produce thousands of charts.
133 +
134 +**`max_*` must be coupled with selectors.** A cap alone silently truncates whatever happens to land in the first N entries — the operator has no say in *which* entities survive. A selector lets the operator pick what's actually important. Cap and selector together: cap protects the system, selector lets the operator drive.
135 +
136 +**Where to filter — depends on what the monitored application exposes:**
137 +
138 +- **Application exposes all instances with no upstream filter.** The collector caps at `max_*` and adds an aggregated **"Other"** chart that sums whatever was capped. Don't silently drop — totals must remain truthful even when individual instances are hidden.
139 +- **Application supports upstream cherry-picking** (e.g. specifying which schemas / databases / queues to monitor at connection time). Push the operator's selector into the application call. Less wire data, less collector work, narrower blast radius if the operator narrows the scope.
140 +- **Application provides aggregations or grouping keys** (totals, group-by-kind, group-by-type, group-by-class). Expose those aggregations as additional charts; let the operator choose which grouping keys to surface. Aggregations are bounded-cardinality views that survive any selector cut and are usually what dashboards actually want — per-instance detail is a drill-down case, not the default.
141 +
142 +**Anti-patterns:**
143 +
144 +- One chart per HTTP route × method × status code → N×M×K series per service.
145 +- Histogram / percentile splits with high-cardinality labels (per-IP, per-tenant, per-trace) → multiplicative blow-up.
146 +- Per-PID charts with no obsolete handler → growth at process churn rate (the bound is here in §2.5; the obsolete handler is the §1.5 concern).
147 +
148 +Pattern reference: `src/go/BEST-PRACTICES.md` (search `max`).
149 +
150 +### 2.6 Configuration discipline
151 +
152 +Tunables live in `config_schema.json` (DYNCFG schema rendered by the dashboard) and `metadata.yaml` (integration page) — both must be complete and mutually consistent. The stock `.conf` shows safe, representative examples — not necessarily every tunable.
153 +
154 +Don't hardcode timeouts, paths, ports, or credentials. Don't let stock conf and schema contradict each other.
155 +
156 +Credentials use the `${env:}/${file:}/${cmd:}/${store:}` indirection — see `src/collectors/SECRETS.md`. Privileged operations route through `src/collectors/utils/ndsudo.c`.
157 +
158 +### 2.7 Generated artifacts are not source
159 +
160 +Several artifacts are produced from upstream definitions and must never be hand-edited:
161 +
162 +- `integrations/<name>.md` — generated from `metadata.yaml` (banner: `DO NOT EDIT THIS FILE DIRECTLY`).
163 +- `ibm.d` modules — generated `README.md`, `metadata.yaml`, `config.go`, `zz_generated_*.go` from `contexts.yaml` via `go generate`.
164 +- Rust plugin charts — derived at compile time via the `charts-derive` proc-macro.
165 +
166 +When a generated file looks wrong, fix the source of truth (`metadata.yaml`, `contexts.yaml`, derive macro input) and regenerate. Note: go.d uses `//go:embed` for static assets — there is no `go generate` step.
167 +
168 +### 2.8 Documentation/configuration consistency
169 +
170 +A new or modified collector ships these in sync:
171 +
172 +- the code
173 +- `metadata.yaml` — drives integration pages, in-app help, alert references
174 +- `config_schema.json` — DYNCFG schema rendered by the dashboard
175 +- stock `.conf` — safe, representative example
176 +- `health.d/*.conf` — alert templates bound to chart `context`
177 +- `README.md` — concise narrative
178 +- if exposing a Function: response shape conforming to `src/plugins.d/FUNCTION_UI_SCHEMA.json`
179 +
180 +Treat them as one unit. Change a unit in code → update `metadata.yaml` in the same commit. Add a config knob → update schema, stock conf, and metadata together.
181 +
182 +### 2.9 Cross-plugin enrichment via netipc
183 +
184 +When one collector needs data from another, use **netipc** — never shell out, open private sockets, poll log files, or reinvent IPC. In-tree libraries:
185 +
186 +- C: `src/libnetdata/netipc/`
187 +- Go: `src/go/pkg/netipc/`
188 +- Rust: `src/crates/netipc/`
189 +
190 +Both clients (consume) and servers (offer) exist in all three languages. Real example: `src/collectors/cgroups.plugin/cgroup-netipc.c` is a netipc server offering cgroup metadata to other plugins. Upstream spec, tests, fuzz suite: <https://github.com/netdata/plugin-ipc>.
191 +
192 +### 2.10 Vnodes for remote targets
193 +
194 +Set `Vnode` in job config; respect it in `Init()` and DYNCFG handlers. See `src/go/plugin/framework/vnodes/` and `src/go/BEST-PRACTICES.md` (search `Vnode`). Past pain: an older refactor had to retroactively split job-name validation per vnode/domain because earlier collectors hadn't accounted for it.
195 +
196 +## 3. Structuring dashboards
197 +
198 +The dashboard is built from charts. The way upstream data turns into charts depends on the ingestion path. Six mechanisms exist; pick the one that matches your collector and *learn how it shapes the result*.
199 +
200 +### 3.1 NIDL framework — the model
201 +
202 +**N**odes, **I**nstances, **D**imensions, **L**abels. This is the conceptual model every other mechanism feeds into. Read `docs/NIDL-Framework.md` before designing metrics. Group dimensions into charts that answer *one operational question*. Use labels for instance and context annotations. Pick the right chart type (`line`, `area`, `stacked`, `heatmap` — see `src/database/rrdset-type.h`) and dimension algorithm (`absolute`, `incremental`, `percentage-of-incremental-row`, `percentage-of-absolute-row` — see `src/database/rrd-algorithm.h`, documented in `src/plugins.d/README.md`).
203 +
204 +Common bugs: `absolute` on a counter (counters are `incremental`); `line` when `stacked` is the right shape (CPU states, disk-time breakdown). Reuse shared metric definitions from `src/collectors/common-contexts/` for C plugins.
205 +
206 +### 3.2 SNMP profiles — declarative spec → NIDL
207 +
208 +SNMP collection is profile-driven. A profile is a YAML document declaring OIDs, metric definitions, table indexing, units, chart families, and selectors. Stock profiles ship from `src/go/plugin/go.d/config/go.d/snmp.profiles/default/`; spec at `src/go/plugin/go.d/collector/snmp/profile-format.md` (~2000 lines).
209 +
210 +Adding or extending SNMP coverage means writing or extending a profile, not adding code. The SNMP topology collector (`snmp_topology`) builds on top of profiles — extending profiles is usually the right starting point for topology work too.
211 +
212 +Past pain: pre-profile SNMP code required per-vendor branches that became unmaintainable. Don't hardcode OID-to-metric mappings inside a custom collector or vendor branch.
213 +
214 +### 3.3 statsd `synthetic_charts` — operator-curated dashboards
215 +
216 +The statsd plugin lets the operator group raw statsd metrics into curated charts via INI configs at `/etc/netdata/statsd.d/*.conf`. Each config defines:
217 +
218 +- `[app]` — match raw metrics by pattern, group them under an application name
219 +- `[dictionary]` — rename raw metric names to display names
220 +- chart sections — declare a chart with `title`, `family`, `context`, `units`, `type`, and explicit `dimension =` lines mapping source metrics to display dimensions
221 +
222 +Wildcard patterns extract dimension names from the matched portion: `dimension = pattern 'myapp.api.*.200' '' last 1 1` creates dimensions named after the wildcard match. Three-layer dimension lookup (dimension name in dictionary → metric name in dictionary → fallback to original). Stock examples: `src/collectors/statsd.plugin/k6.conf`, `src/collectors/statsd.plugin/asterisk.conf`. Full spec: `src/collectors/statsd.plugin/README.md` lines 397-639.
223 +
224 +This is the most operator-controllable shaping mechanism — the dashboard is whatever the operator declares.
225 +
226 +### 3.4 OTEL mappings — per-metric YAML routing
227 +
228 +Netdata's OTEL plugin (`src/crates/netdata-otel/otel-plugin/`) accepts any OTLP gRPC metric. Mapping is **generic by default** — all resource attributes, scope attributes, and data point attributes become chart labels — but the operator controls routing via per-metric YAML files at `/etc/netdata/otel.d/v1/metrics/*.yaml`. Key knobs:
229 +
230 +- `instrumentation_scope.name` / `version` — regex match to scope an entry to a specific OTel instrumentation
231 +- `dimension_attribute_key` — which data point attribute becomes the dimension name (default: `"value"`); other attributes become chart labels
232 +- `interval_secs`, `grace_period_secs` — per-metric timing overrides
233 +
234 +Aggregation temporality drives the chart algorithm: Gauge → absolute, Sum delta → DeltaSum, Sum cumulative monotonic → CumulativeSum, Sum cumulative non-monotonic → treated as Gauge (`src/crates/netdata-otel/otel-plugin/src/chart.rs:84`).
235 +
236 +The plugin does **not** recognize OTel semantic conventions specifically (`host.name`, `service.name`, `deployment.environment`) — they pass through as labels. Cardinality control is `metrics.max_new_charts_per_request` in `otel.yaml`. Stock examples: `src/crates/netdata-otel/otel-plugin/configs/otel.d/v1/metrics/`.
237 +
238 +### 3.5 Prometheus — deterministic; shape upstream to shape dashboard
239 +
240 +The generic Prometheus scraper (`src/go/plugin/go.d/collector/prometheus/`) auto-maps from the exposition format with no per-metric synthetic shaping:
241 +
242 +- metric name → chart ID + dimension ID
243 +- Prometheus labels → Netdata chart labels (with optional `label_prefix`)
244 +- type (`counter`, `gauge`, `histogram`, `summary`) → chart type and dimension algorithm
245 +- histograms and summaries explode into 3 charts each (buckets/quantiles, `_sum`, `_count`)
246 +- recognized suffixes: `_total` (counter), `_bucket` + `le` label (histogram), `_sum`, `_count`, `quantile` label (summary), `_info` (skipped)
247 +- unit suffixes drive the units string: `_seconds`, `_bytes`, `_hertz`
248 +
249 +Operator controls are **scoping, not shaping**: time-series **selectors** (allow/deny on metric name and label values, `src/go/plugin/go.d/collector/prometheus/README.md:110-127`) and `fallback_type` glob patterns for untyped metrics. There is **no** equivalent of statsd `synthetic_charts` — you cannot group disparate Prometheus metrics into a composite chart Netdata-side. To shape the dashboard, shape the upstream exporter: rename metrics, add labels, fix types upstream.
250 +
251 +### 3.6 Chart priorities
252 +
253 +Chart priorities (`priority` field in C, `Priority` in Go) drive UI ordering. C plugins follow conventions in `src/collectors/all.h`. Don't pick priorities arbitrarily; mirror an adjacent collector's range.
254 +
255 +## 4. Production-quality criteria & pre-PR checklist
256 +
257 +A collector is *production-quality* when it satisfies all of:
258 +
259 +- **Survives target unavailability for hours** without log floods, fd leaks, memory growth, or runaway retries.
260 +- **Bounded memory under failure** — buffers do not grow on parse errors or stuck connections.
261 +- **No fd / goroutine / thread leaks** across `Cleanup()` cycles or job reloads.
262 +- **Cycle-latency budget respected** — `Collect()` finishes well under one cycle even on a slow target.
263 +- **Graceful with partial / malformed upstream responses** — parser does not crash, log-flood, or skip downstream collection.
264 +- **High-cardinality entities bounded** via `max_*` and selectors so the operator can scope them.
265 +- **Disappeared entities obsoleted** so the dashboard reflects what is actually being collected (this applies even at low cardinality).
266 +- **IDs (chart context, chart ID, dimension ID, instance labels) are stable** — never renamed without a migration plan.
267 +
268 +### Pre-PR checklist
269 +
270 +1. Did I research the **current** spec/protocol/application from authoritative sources, not just from prior knowledge?
271 +2. For ambiguous specs: did I cross-check against 2–3 popular open-source monitoring projects?
272 +3. Do all metrics have units, chart families, and meaningful names? Did NIDL inform the grouping? Are chart types and dimension algorithms correct (`incremental` for counters, etc.)?
273 +4. Are gaps preserved (no zero defaults for missing values)?
274 +5. Does the collection cycle allocate, log per iteration, or reconnect every cycle?
275 +6. Do error logs answer *what operation, what target, what was expected vs observed*?
276 +7. Are config knobs in `config_schema.json` and `metadata.yaml`? Does the stock `.conf` show a representative example?
277 +8. Are alerts present in `health.d/`?
278 +9. Is `README.md` updated? (Not the generated `integrations/<name>.md`.)
279 +10. For remote targets: is vnode wiring done?
280 +11. For SNMP: did I extend a profile rather than hardcode OIDs?
281 +12. For statsd / OTEL: did I document and ship the operator-side config (synthetic_charts file or OTEL mapping YAML)?
282 +13. For Prometheus scraping: are selectors correct? Are untyped metrics handled?
283 +14. For cross-plugin enrichment: am I using netipc?
284 +15. For Functions: does the response conform to one of the six shapes? Non-blocking with respect to the collection loop? Schema-validated?
285 +16. For ibm.d only: did I run `go generate` after touching `contexts.yaml`?
286 +17. For new go.d modules: are all four wiring steps done (init.go, go.d.conf, stock conf, README)?
287 +18. Tests: real fixtures or real instances? Would they catch the bug I just fixed?
288 +19. High-cardinality labels / instances: bounded by `max_*` + selectors? Aggregated "Other" bucket or upstream-supplied aggregation present where applicable?
289 +20. Entities that can go away: obsoleted when the collector knows they're gone? Anti-flip-flop window applied where churn is expected?
290 +21. Production-quality criteria above — would this collector survive hours of target outage without leaks or log floods?
291 +
292 +## 5. Plugins and frameworks — what's available and where
293 +
294 +Reference section. Use it after the mental model and best practices have framed your task.
295 +
296 +### 5.1 The plugin landscape
297 +
298 +| Family | Lang | Platforms | Where in repo | Scope |
299 +|---|---|---|---|---|
300 +| `proc.plugin` | C | Linux | `src/collectors/proc.plugin/` | Kernel `/proc` and `/sys` |
301 +| `apps.plugin` | C | Linux/FreeBSD/macOS/Windows | `src/collectors/apps.plugin/` | Per-process and per-user/group; `processes` Function |
302 +| `cgroups.plugin` | C | Linux | `src/collectors/cgroups.plugin/` | Containers and control groups |
303 +| `ebpf.plugin` | C + eBPF | Linux | `src/collectors/ebpf.plugin/` | Kernel function tracing |
304 +| `network-viewer.plugin` | C | Linux | `src/collectors/network-viewer.plugin/` | L3/L4 sockets; `topology:` Functions |
305 +| `systemd-journal.plugin` / `windows-events.plugin` | C | Linux/Windows | `src/collectors/{systemd-journal,windows-events}.plugin/` | Log/event explorers via Functions |
306 +| `systemd-units.plugin` | C | Linux | `src/collectors/systemd-units.plugin/` | systemd unit state |
307 +| `windows.plugin` | C | Windows | `src/collectors/windows.plugin/` | Windows performance counters |
308 +| `freebsd.plugin` / `macos.plugin` | C | platform-specific | `src/collectors/{freebsd,macos}.plugin/` | OS analogs of `proc.plugin` |
309 +| `statsd.plugin` | C | All | `src/collectors/statsd.plugin/` | StatsD ingestion + synthetic_charts |
310 +| `log2journal` | C | Linux | `src/collectors/log2journal/` | Parse application logs into the systemd journal |
311 +| Niche C plugins | C | various | `src/collectors/<name>.plugin/` | freeipmi, nfacct, tc, xenstat, debugfs, diskspace, slabinfo, idlejitter, timex, cups, ioping, perf |
312 +| `go.d.plugin` | Go (no CGO) | All | `src/go/plugin/go.d/` | 132 application integrations |
313 +| `ibm.d.plugin` | Go + CGO | Linux, IBM i | `src/go/plugin/ibm.d/modules/` | IBM workloads (DB2, IBM i / AS-400, IBM MQ, WebSphere) |
314 +| `netflow-plugin` | Rust | Linux | `src/crates/netflow-plugin/` | NetFlow v5/v9, IPFIX, sFlow |
315 +| `netdata-otel` | Rust | Linux | `src/crates/netdata-otel/otel-plugin/` | OpenTelemetry ingestion |
316 +| `netdata-log-viewer` | Rust | Linux | `src/crates/netdata-log-viewer/` | OTEL signal viewer + journal Function backend |
317 +| `charts.d.plugin` / `python.d.plugin` | Bash / Python | All | `src/collectors/{charts,python}.d.plugin/` | **Legacy** — do not add new modules |
318 +
319 +Path conventions: internal C plugins → `src/collectors/<name>.plugin/`; Go orchestrators → `src/go/plugin/{go.d,ibm.d}/`; Rust plugins → `src/crates/<name>/`.
320 +
321 +### 5.2 Routing by task
322 +
323 +| If you are doing… | Start with |
324 +|---|---|
325 +| New off-the-shelf application integration (no CGO) | `src/go/plugin/go.d/docs/how-to-write-a-collector.md`; V2 reference: `src/go/plugin/go.d/collector/ping/` |
326 +| New IBM workload integration (CGO) | `src/go/plugin/ibm.d/AGENTS.md`, `src/go/plugin/ibm.d/framework/README.md` |
327 +| New Rust plugin | SDK at `src/crates/netdata-plugin/`; reference: `src/crates/netflow-plugin/` |
328 +| New SNMP profile (no code change) | `src/go/plugin/go.d/collector/snmp/profile-format.md` |
329 +| New interactive Function | `src/go/plugin/framework/functions/README.md`, `src/plugins.d/FUNCTION_UI_SCHEMA.json`, `src/plugins.d/FUNCTION_UI_DEVELOPER_GUIDE.md` |
330 +| Topology work | `src/go/pkg/topology/`, `src/go/plugin/go.d/collector/snmp_topology/`, `src/collectors/network-viewer.plugin/` |
331 +| Auto-discovery for a new go.d module | rules under `src/go/plugin/go.d/config/go.d/sd/`; engine: `src/go/plugin/agent/discovery/` |
332 +| OTEL ingestion | `src/crates/netdata-otel/otel-plugin/` |
333 +| Log ingestion (parse → journal) | `src/collectors/log2journal/` and `log2journal.d/` rules |
334 +| New external plugin in any language | `src/plugins.d/README.md` (PLUGINSD protocol) |
335 +| New internal C plugin | `src/collectors/README.md`; mirror an adjacent collector |
336 +| Cross-plugin data enrichment | netipc libraries (§5.4) |
337 +| Privileged operations | `src/collectors/utils/ndsudo.c` |
338 +| Credentials in config | `src/collectors/SECRETS.md` |
339 +
340 +### 5.3 go.d V1 / V2 reality check
341 +
342 +Only **5 of 132** go.d collectors use V2: `ping`, `mysql`, `azure_monitor`, `powerstore`, `powervault`. The big reference docs (`src/go/BEST-PRACTICES.md`, `src/go/COLLECTOR-LIFECYCLE.md`) describe V1. V2 building blocks have framework READMEs (`src/go/plugin/framework/charttpl/README.md`, `src/go/plugin/framework/chartengine/README.md`, `src/go/pkg/metrix/README.md`); there is no end-to-end V2 tutorial beyond `how-to-write-a-collector.md` plus the `ping/` source.
343 +
344 +**For new go.d modules: use V2.** Mirror `src/go/plugin/go.d/collector/ping/` (or `mysql/` for V2 + Functions). Copying any other module mirrors V1 and the maintainers will ask you to migrate.
345 +
346 +V2 imports: `github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi` and `.../pkg/metrix`. The `CollectorV2` interface lives at `src/go/plugin/framework/collectorapi/collector.go`.
347 +
348 +Lifecycle semantics: `Init()` is one-time setup (failure disables permanently); `Check()` is auto-detection probe (failure disables, retried later); `Collect()` is the hot path (every `update_every` seconds); `Cleanup()` is guaranteed on shutdown.
349 +
350 +**Silent-failure trap (go.d).** A new go.d module compiles and tests pass even when it is *not loaded* by the plugin at runtime. Loading requires four wiring steps: import in `src/go/plugin/go.d/collector/init.go`, `modules:` toggle in `src/go/plugin/go.d/config/go.d.conf`, stock job config at `src/go/plugin/go.d/config/go.d/<name>.conf`, and entry in `src/go/plugin/go.d/README.md`. Same trap applies to `ibm.d`.
351 +
352 +### 5.4 ibm.d, Rust SDK, internal C, PLUGINSD
353 +
354 +- **ibm.d** (CGO, IBM-vendor workloads) — use the ibm.d framework with `go generate` after touching `contexts.yaml`. See `src/go/plugin/ibm.d/AGENTS.md`. Don't reach for ibm.d for non-IBM CGO needs — the framework is shaped around vendor drivers; CGO outside the IBM ecosystem is a design discussion.
355 +- **Rust SDK** at `src/crates/netdata-plugin/` — modules `bridge/`, `protocol/`, `rt/`, `charts-derive/`, `schema/`, `types/`, `error/`. Documentation lives in `lib.rs` doc-comments — there is no README. New Rust crates go into the `src/crates/Cargo.toml` workspace. Reference impl: `src/crates/netflow-plugin/`.
356 +- **Internal C plugins** — mirror an adjacent collector under `src/collectors/<name>.plugin/`; reuse `src/libnetdata/`. `libnetdata.h` includes most of libnetdata so individual headers are usually unnecessary. Allocators with the `z` suffix (`mallocz`, `callocz`, `strdupz`, `freez`) handle failures via `fatal()`; `freez(NULL)` is safe. JSON parsing: json-c. JSON generation: `buffer_json_*`. Linked lists: `DOUBLE_LINKED_LIST_*` macros.
357 +- **PLUGINSD external plugins (any language)** — spec at `src/plugins.d/README.md`. Useful when implementation language is dictated by an SDK that go.d / ibm.d / Rust cannot accommodate.
358 +
359 +**Don't:**
360 +- write new go.d modules against V1
361 +- add modules to `charts.d.plugin` or `python.d.plugin`
362 +- run `go generate` for go.d (no `//go:generate` directives — uses `//go:embed`)
363 +- add new third-party Go modules or system-library dependencies casually — they ship to every Netdata install; check with maintainers if non-trivial
364 +
365 +### 5.5 Build / dev loop
366 +
367 +- go.d unit tests: `cd src/go && go test ./plugin/go.d/collector/<name>/...`
368 +- Single-module dev run: `go run ./cmd/godplugin -m <name> -d`
369 +- Rust: `cargo test -p <crate>`
370 +- Whole-project install: `./netdata-installer.sh`
371 +
372 +## 6. Dealing with data types
373 +
374 +A collector ingests one or more of these data types. Each has its own pattern.
375 +
376 +### 6.1 Metrics (time-series numeric data)
377 +
378 +The default. Streams as `BEGIN/SET/END` (PLUGINSD) or framework equivalents. Shape via NIDL (§3). Storage is the dbengine; alerts bind to chart `context`; anomaly detection / ML jobs run continuously. Every metric travels via streaming to parents and to Netdata Cloud — cardinality matters everywhere.
379 +
380 +### 6.2 Logs
381 +
382 +Two paths:
383 +
384 +- **Structured journaling.** `src/collectors/log2journal/` parses application/access logs (configurable YAML rules in `log2journal.d/`, e.g. `nginx-json.yaml`, `default.yaml`) and writes structured fields into the systemd journal. The `systemd-journal.plugin` then exposes the entries via a Function (the log explorer in the Netdata UI).
385 +- **OTEL log signals.** `src/crates/netdata-log-viewer/` ingests OTEL logs and exposes them as Functions in the dashboard.
386 +
387 +Platform-specific events: `windows-events.plugin` (Windows event log).
388 +
389 +Logs are **not metrics**. Don't try to derive metrics from logs in the collection loop — emit logs as logs, then build metrics separately if needed.
390 +
391 +### 6.3 Live snapshots (Functions)
392 +
393 +Interactive, on-demand tabular data: process lists, network connections, FDB tables, log entries, journal queries, topology snapshots, flow records. Functions complement metrics; they don't replace them.
394 +
395 +Build a Function when the answer is **interactive/tabular live data**. If the answer is a numeric time series, that's a metric.
396 +
397 +Response shape is one of `info_response`, `data_response`, `topology_response`, `flows_response`, `error_response`, `not_modified_response` (defined in `src/plugins.d/FUNCTION_UI_SCHEMA.json`). For Go, use builders in `src/go/pkg/funcapi/`. For Rust, implement the `FunctionHandler` trait from the SDK runtime (`src/crates/netdata-plugin/rt/`).
398 +
399 +Functions run concurrently with the collection loop — they must not block it. Validate during development with `src/go/tools/functions-validation/`.
400 +
401 +Reference implementations: `src/collectors/network-viewer.plugin/` (topology + connections), `src/collectors/systemd-journal.plugin/` (log explorer), `src/collectors/apps.plugin/` (processes).
402 +
403 +Backend docs: `src/go/plugin/framework/functions/README.md` (Go), `src/crates/netdata-plugin/rt/src/lib.rs` (Rust `FunctionHandler`). UI/protocol: `src/plugins.d/FUNCTION_UI_DEVELOPER_GUIDE.md`, `src/plugins.d/FUNCTION_UI_REFERENCE.md`.
404 +
405 +### 6.4 Topology / interconnections / links
406 +
407 +Topology is its own data type — directed/undirected graphs of nodes and links. Sources and consumers:
408 +
409 +- **SNMP-discovered topology** (`src/go/plugin/go.d/collector/snmp_topology/`) — LLDP/CDP neighbors, BRIDGE-MIB FDB, Q-BRIDGE FDB, ARP tables, STP. Builds on SNMP profiles; extending profiles is usually the right starting point.
410 +- **Live socket topology** (`src/collectors/network-viewer.plugin/`) — local L3/L4 sockets and their inferred connections.
411 +- **Streaming graph** (`src/streaming/`) — Netdata parent/child topology.
412 +- **Topology library** at `src/go/pkg/topology/` — shared types and providers consumed by the topology collectors.
413 +
414 +Topology is consumed via Functions (`topology:*` family), not via metrics. The cardinality of network edges is too high for time-series storage and the use case is interactive lookup.
415 +
416 +### 6.5 Data enrichment via netipc
417 +
418 +When a collector needs data from another collector to enrich its output (a network collector wanting cgroup labels, an `apps` collector wanting cgroup PIDs, a flow collector wanting interface metadata), use **netipc**. Don't shell out, don't open private sockets, don't poll log files.
419 +
420 +Both client and server roles exist in C, Go, and Rust:
421 +
422 +- C: `src/libnetdata/netipc/`
423 +- Go: `src/go/pkg/netipc/`
424 +- Rust: `src/crates/netipc/`
425 +
426 +`cgroups.plugin` (`src/collectors/cgroups.plugin/cgroup-netipc.c`) is a real example of a netipc server offering cgroup metadata to other plugins. Upstream spec, tests, fuzz suite: <https://github.com/netdata/plugin-ipc>.
427 +
428 +## 7. Common practices per collector domain
429 +
430 +These are descriptive patterns — what existing Netdata collectors do. Use them as defaults; deviate with reason.
431 +
432 +### 7.1 Database collectors
433 +
434 +DB collectors typically pair metrics (uptime, connections, query rates, replication lag, lock counts, cache hit ratios) with **Functions for live query analysis**: top queries, slow queries, currently-running queries, locks. Real examples:
435 +
436 +- **MySQL** (`src/go/plugin/go.d/collector/mysql/`) — metrics + `mysqlfunc/top_queries.go` + processlist via `collect_process_list.go`.
437 +- **PostgreSQL** (`src/go/plugin/go.d/collector/postgres/`) — metrics + `func_top_queries.go` + `func_running_queries.go`, dispatched through `func_router.go`.
438 +- MongoDB / Redis are metrics-only today, but the same Function pattern fits if the use case demands it.
439 +
440 +If you build a DB collector with metrics only, expect the maintainers to ask why you didn't add a query Function — the operator value of seeing "what's slow right now" is high and the pattern is established.
441 +
442 +### 7.2 Network and SNMP collectors
443 +
444 +Network/SNMP collectors typically pair metrics with **topology Functions** and FDB / ARP / LLDP enrichment:
445 +
446 +- **`snmp` + `snmp_topology`** (`src/go/plugin/go.d/collector/snmp_topology/`) — topology Functions (`func_topology.go`, `func_topology_handler.go`, `func_topology_managed_focus.go`, `func_topology_options.go`, `func_topology_presentation.go`, `func_topology_depth.go`) on top of SNMP profile data.
447 +- **`network-viewer.plugin`** (`src/collectors/network-viewer.plugin/`) — `topology:` Functions for live socket-level topology.
448 +
449 +Per-device metrics need **vnode wiring** (each managed device is a vnode). FDB/ARP/STP data lands as topology Functions, not metrics — the cardinality is too high for metrics and the use case is interactive lookup.
450 +
451 +### 7.3 Container / orchestration collectors
452 +
453 +Container collectors pair container metrics with **enrichment via netipc**:
454 +
455 +- `cgroups.plugin` exposes a netipc server (`src/collectors/cgroups.plugin/cgroup-netipc.c`) that other plugins query to map PIDs/cgroups to container/pod identity.
456 +- `apps.plugin` and `network-viewer.plugin` consume this enrichment to label processes and connections with container metadata.
457 +
458 +When adding a new orchestration source (Kubernetes API, Docker events, Nomad, etc.), think about who downstream needs the labels and whether to expose them via netipc.
459 +
460 +### 7.4 Web servers and reverse proxies
461 +
462 +Web server collectors pair metrics (requests, status codes, latency, upstream errors) with **access-log Functions** when the access log is structured:
463 +
464 +- `log2journal` parses NGINX/Apache/HAProxy access logs (rules under `src/collectors/log2journal/log2journal.d/`).
465 +- The journal explorer Function makes the parsed entries searchable in the dashboard.
466 +
467 +If the application's log format is closed or unstructured, only metrics are practical.
468 +
469 +### 7.5 Flow protocols (NetFlow / sFlow / IPFIX)
470 +
471 +The Rust `netflow-plugin` (`src/crates/netflow-plugin/`) ingests flows and exposes them via Functions (`flows_response` shape). Flows are per-record, high-cardinality, and not suitable for traditional metric storage. Reference fixtures and provenance discipline live under `src/crates/netflow-plugin/testdata/`. Topology enrichment (interface names, AS metadata) typically comes from netipc or from SNMP-collected interface data.
472 +
473 +### 7.6 Application servers and middleware
474 +
475 +Java app servers, message queues, application middleware — JMX/HTTP/protobuf metrics are the default; some pair with log exploration via journal or OTEL log signals when the workflow benefits from it. Mirror the closest existing collector.
476 +
477 +### 7.7 OS/kernel collectors
478 +
479 +Internal C plugins under `src/collectors/`. Reuse shared metric definitions from `src/collectors/common-contexts/`; follow chart-priority conventions in `src/collectors/all.h`; lean on `src/libnetdata/` rather than reimplementing utilities.
480 +
481 +## 8. Canonical documentation pointers
482 +
483 +| Topic | Open when | Path |
484 +|---|---|---|
485 +| NIDL framework | designing metrics, labels, charts | `docs/NIDL-Framework.md` |
486 +| Chart types and dimension algorithms | choosing chart shape and metric algorithm | `src/database/rrdset-type.h`, `src/database/rrd-algorithm.h` |
487 +| Chart priorities (C) | dashboard ordering convention | `src/collectors/all.h` |
488 +| Shared metric definitions (C) | reusing common contexts | `src/collectors/common-contexts/` |
489 +| Plugin types and privileges | choosing where to add a collector | `src/collectors/README.md` |
490 +| External plugin protocol | non-Go external plugin | `src/plugins.d/README.md` |
491 +| go.d V2 authoring | adding a `go.d` module | `src/go/plugin/go.d/docs/how-to-write-a-collector.md` |
492 +| go.d V1 best practices / lifecycle | working in legacy V1 module | `src/go/BEST-PRACTICES.md`, `src/go/COLLECTOR-LIFECYCLE.md` |
493 +| Functions backend (Go / Rust) | implementing a Function | `src/go/plugin/framework/functions/README.md`, `src/crates/netdata-plugin/rt/src/lib.rs` |
494 +| Functions UI schema & guides | response shapes and patterns | `src/plugins.d/FUNCTION_UI_SCHEMA.json`, `src/plugins.d/FUNCTION_UI_DEVELOPER_GUIDE.md`, `src/plugins.d/FUNCTION_UI_REFERENCE.md` |
495 +| Functions validator | E2E + schema validation | `src/go/tools/functions-validation/README.md` |
496 +| ibm.d framework | starting `ibm.d` work | `src/go/plugin/ibm.d/AGENTS.md`, `src/go/plugin/ibm.d/framework/README.md` |
497 +| Rust plugin SDK | new Rust plugin | `src/crates/netdata-plugin/` (`rt/`, `protocol/`, `bridge/`, `charts-derive/`, `schema/`, `types/`, `error/`) |
498 +| Rust NetFlow plugin | NetFlow / sFlow / IPFIX work | `src/crates/netflow-plugin/` |
499 +| OTEL ingestion mappings | per-metric YAML routing | `src/crates/netdata-otel/otel-plugin/` (configs under `configs/otel.d/v1/metrics/`) |
500 +| SNMP profile format | adding/extending an SNMP profile | `src/go/plugin/go.d/collector/snmp/profile-format.md` |
501 +| SNMP stock profiles | starting from a known device | `src/go/plugin/go.d/config/go.d/snmp.profiles/default/` |
502 +| statsd synthetic_charts | operator-curated dashboards | `src/collectors/statsd.plugin/README.md` (lines 397-639) |
503 +| Prometheus mapping | generic exposition scrape | `src/go/plugin/go.d/collector/prometheus/README.md` |
504 +| log2journal | parsing application logs into the journal | `src/collectors/log2journal/log2journal.d/` |
505 +| Auto-discovery rules | adding service-detection rules | `src/go/plugin/go.d/config/go.d/sd/{net_listeners,docker,snmp,http}.conf` |
506 +| Topology library | topology providers in Go | `src/go/pkg/topology/` |
507 +| netipc cross-plugin enrichment | C / Go / Rust | `src/libnetdata/netipc/`, `src/go/pkg/netipc/`, `src/crates/netipc/` |
508 +| DYNCFG protocol | dynamic configuration | `src/plugins.d/DYNCFG.md`, `docs/developer-and-contributor-corner/dyncfg.md` |
509 +| Health alerts reference | alert template authoring | `src/health/REFERENCE.md`, `src/health/alert-configuration-ordering.md` |
510 +| Integrations pipeline | doc generation from `metadata.yaml` | `integrations/README.md` |
511 +| Credentials in config | `${env:}/${file:}/${cmd:}/${store:}` | `src/collectors/SECRETS.md` |
512 +| Privileged operations | restricted setuid helper | `src/collectors/utils/ndsudo.c` |
513 +
514 +## 9. Maintaining this skill
515 +
516 +This skill is **live**. When you find a gap, an outdated pointer, a new pattern, or a bad practice not yet captured, propose changes to this file in the same PR that exposed the issue. When fixing a wrong pointer, also record what was misleading about the prior text — future readers see both the corrected map and the failure mode that produced it. Mention the change in the PR description so it gets reviewed consciously rather than skimmed.
.agents/sow/SOW.template.md
+10 -1
@@ -4,6 +4,8 @@
4
5 Status: open | in-progress | paused | completed | closed
6
7 +`completed` is the successful terminal status. `done` is a directory name, not a status value. Do not use `Status: done` or `Status: complete`.
8 +
9 Sub-state: <short current truth>
10
11 ## Requirements
@@ -73,6 +75,13 @@ Risk and blast radius:
75
76 - <Regression, compatibility, performance, security, data loss, migration, rollout, and operational risks.>
77
78 +<<<<<<< Updated upstream
79 +=======
80 +Sensitive data handling plan:
81 +
82 +- <Whether the work may expose secrets, credentials, bearer tokens, SNMP communities, community/customer data, personal data, non-private customer-identifying IPs, private endpoints, or proprietary incident details; how evidence will be redacted in SOWs, specs, docs, skills, instructions, and code comments.>
83 +
84 +>>>>>>> Stashed changes
85 Implementation plan:
86
87 1. <Ordered chunk with scope, dependencies, and likely files/modules.>
@@ -139,7 +148,7 @@ Artifact maintenance gate:
148 - Specs: <updated .agents/sow/specs/ path or evidence-backed reason no update was needed>
149 - End-user/operator docs: <updated docs/runbooks/help paths or evidence-backed reason none were affected>
150 - End-user/operator skills: <updated output/reference skill paths or evidence-backed reason none were affected>
142 -- SOW lifecycle: <status/directory checked; split/merge/follow-up/regression handling recorded>
151 +- SOW lifecycle: <status/directory checked; if successful close, `Status: completed` and move to `.agents/sow/done/` are committed together with the work in one commit unless user explicitly requested a different split; split/merge/follow-up/regression handling recorded>
152
153 Specs update:
154
.agents/sow/audit.sh
+244
@@ -66,6 +66,101 @@ read_sow_status() {
66 ' "$1" 2>/dev/null
67 }
68
69 +<<<<<<< Updated upstream
70 +=======
71 +sensitive_scan_files() {
72 + [ -f ./AGENTS.md ] && printf '%s\n' ./AGENTS.md
73 + [ -f ./AGENTS.md.pre-sow.bak ] && printf '%s\n' ./AGENTS.md.pre-sow.bak
74 + [ -f ./SKILL.md ] && printf '%s\n' ./SKILL.md
75 + [ -f ./SOW-status.md ] && printf '%s\n' ./SOW-status.md
76 + for sow_dir in ./.agents/sow/pending ./.agents/sow/current ./.agents/sow/specs; do
77 + [ -d "$sow_dir" ] && find "$sow_dir" -type f -name '*.md' 2>/dev/null
78 + done
79 + [ -d ./.agents/skills ] && find ./.agents/skills -type f \( -name '*.md' -o -name 'SKILL.md' -o -name '*.yaml' -o -name '*.yml' \) 2>/dev/null
80 + if [ "${SOW_AUDIT_SENSITIVE_FULL_HISTORY:-0}" = "1" ]; then
81 + find . -path ./.git -prune -o -type f \( -name '*.md' -o -name '*.rst' -o -name '*.adoc' -o -name '*.txt' -o -name '*.yaml' -o -name '*.yml' \) -print 2>/dev/null
82 + elif [ "${SOW_AUDIT_SENSITIVE_CHANGED:-0}" = "1" ] && git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
83 + {
84 + git diff --name-only --diff-filter=ACMR HEAD -- 2>/dev/null
85 + git diff --cached --name-only --diff-filter=ACMR -- 2>/dev/null
86 + git ls-files -o --exclude-standard 2>/dev/null
87 + } | awk '
88 + /\.(md|rst|adoc|txt|yaml|yml|json|toml|ini|conf|cfg|env|sh|bash|zsh|py|js|jsx|ts|tsx|go|rs|c|h|hpp|cpp|java|rb|php|lua|sql)$/ { print "./" $0; next }
89 + /(^|\/)(README|CHANGELOG|CONTRIBUTING|Dockerfile|Makefile)(\..*)?$/ { print "./" $0; next }
90 + /(^|\/)AGENTS\.md$/ { print "./" $0; next }
91 + '
92 + fi
93 +}
94 +
95 +scan_sensitive_file() {
96 + local file="$1"
97 + perl -ne '
98 + chomp;
99 + my $line = $_;
100 + my @hits;
101 +
102 + sub is_public_customer_ip {
103 + my ($ip) = @_;
104 + my @o = split(/\./, $ip);
105 + return 0 unless @o == 4;
106 + return 0 if grep { $_ !~ /^\d+$/ || $_ < 0 || $_ > 255 } @o;
107 + return 0 if $o[0] == 10;
108 + return 0 if $o[0] == 172 && $o[1] >= 16 && $o[1] <= 31;
109 + return 0 if $o[0] == 192 && $o[1] == 168;
110 + return 0 if $o[0] == 127;
111 + return 0 if $o[0] == 169 && $o[1] == 254;
112 + return 0 if $o[0] == 100 && $o[1] >= 64 && $o[1] <= 127;
113 + return 0 if $o[0] == 0;
114 + return 0 if $o[0] >= 224;
115 + return 0 if $o[0] == 192 && $o[1] == 0 && $o[2] == 2;
116 + return 0 if $o[0] == 198 && $o[1] == 51 && $o[2] == 100;
117 + return 0 if $o[0] == 203 && $o[1] == 0 && $o[2] == 113;
118 + return 1;
119 + }
120 +
121 + push @hits, "private-key-material" if $line =~ /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/;
122 + push @hits, "aws-access-key" if $line =~ /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/;
123 + push @hits, "github-token" if $line =~ /\b(?:github_pat_[A-Za-z0-9_]{20,}|gh[pousr]_[A-Za-z0-9_]{20,})\b/;
124 + push @hits, "slack-token" if $line =~ /\bxox[baprs]-[A-Za-z0-9-]{20,}\b/;
125 + push @hits, "openai-key" if $line =~ /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/;
126 + push @hits, "google-api-key" if $line =~ /\bAIza[0-9A-Za-z_-]{20,}\b/;
127 + push @hits, "jwt" if $line =~ /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/;
128 + push @hits, "credentialed-url" if $line =~ m{[a-z][a-z0-9+.-]*://[^/\s:@]+:[^/\s:@]+@}i;
129 + push @hits, "bearer-token" if $line =~ /\bBearer\s+[A-Za-z0-9._~+\/=-]{16,}\b/i && $line !~ /\b(REDACTED|EXAMPLE|PLACEHOLDER|YOUR[_-]?TOKEN|TOKEN|API[_-]?KEY|ACCESS[_-]?TOKEN)\b/i;
130 +
131 + if ($line =~ /\b(?:pass(?:word)?|passwd|pwd|api[_-]?key|secret|token|client[_-]?secret|private[_-]?key|access[_-]?key)\b\s*[:=]\s*["'\''`]?([^"'\''`\s<>{}\[\]&,]{8,})/i) {
132 + my $value = lc $1;
133 + push @hits, "credential-assignment" unless $value =~ /^(redacted|example|placeholder|changeme|change-me|xxx|xxxx|null|none|your[_-]?|dummy|sample|fake|test)/ || $value =~ /^\$/ || $value =~ /^(config|settings|options|opts|env|process\.env|os\.environ)\./ || $value =~ /^[a-z_][a-z0-9_.]*(token|secret|key|password)[a-z0-9_.]*$/;
134 + }
135 +
136 + if ($line =~ /\b(?:snmp[_-]?)?(?:community|community[_-]?string|rocommunity|rwcommunity)\b\s*[:=]\s*["'\''`]?([^"'\''`\s<>{}\[\]]{3,})/i) {
137 + my $value = lc $1;
138 + push @hits, "snmp-community" unless $value =~ /^(redacted|example|placeholder|changeme|change-me|xxx|xxxx|null|none)$/;
139 + }
140 +
141 + if ($line =~ /\b(?:customer|client|tenant|account|organization|org|community[ _-]?member)[ _-](?:name|id|identifier)\b\s*[:=]\s*["'\''`]?([^"'\''`<>\[\]{}][^"'\''`<>\[\]{}]{2,})/i) {
142 + my $value = $1;
143 + $value =~ s/^\s+|\s+$//g;
144 + push @hits, "customer-or-private-identifier" unless $value =~ /^(redacted|example|placeholder|customer-|client-|tenant-|account-|org-|user|none|null)/i;
145 + }
146 +
147 + if ($line =~ /\b[A-Z0-9._%+\-]+@[A-Z0-9.\-]+\.[A-Z]{2,}\b/i) {
148 + push @hits, "email-address" unless $line =~ /\b(example\.com|example\.org|example\.net|localhost)\b/i;
149 + }
150 +
151 + if ($line =~ /\b(customer|client|tenant|account|community member|support|production|prod|log|trace|request|source ip|remote ip|x-forwarded-for|host ip)\b/i) {
152 + while ($line =~ /\b((?:\d{1,3}\.){3}\d{1,3})\b/g) {
153 + push @hits, "public-ip-address" if is_public_customer_ip($1);
154 + }
155 + }
156 +
157 + for my $hit (@hits) {
158 + print "$ARGV:$.:$hit\n";
159 + }
160 + ' "$file" 2>/dev/null
161 +}
162 +
163 +>>>>>>> Stashed changes
164 # --- Marker check ---
165 echo "${BLUE}-- initialization marker --${NC}"
166 if [ -f ./AGENTS.md ]; then
@@ -90,6 +185,11 @@ required_sections=(
185 "### Roles"
186 "### Git Worktrees"
187 "### Pre-Implementation Gate"
188 +<<<<<<< Updated upstream
189 +=======
190 + "### SOW Completion And Commit"
191 + "### Regressions"
192 +>>>>>>> Stashed changes
193 "### Project Skills"
194 "### Specs"
195 "### Project-specific overrides"
@@ -178,10 +278,40 @@ if [ -f ".agents/sow/SOW.template.md" ]; then
278 echo " ${RED}--${NC} template missing ## Pre-Implementation Gate"
279 sow_template_pre_impl_missing=1
280 fi
281 +<<<<<<< Updated upstream
282 +=======
283 + if grep -q "^Sensitive data handling plan:$" ".agents/sow/SOW.template.md" 2>/dev/null && grep -q "^Sensitive data gate:$" ".agents/sow/SOW.template.md" 2>/dev/null; then
284 + echo " ${GREEN}OK${NC} template includes sensitive data gates"
285 + sow_template_sensitive_gate_missing=0
286 + else
287 + echo " ${RED}--${NC} template missing sensitive data handling plan or gate"
288 + sow_template_sensitive_gate_missing=1
289 + fi
290 + if grep -q "^Open-source reference evidence:$" ".agents/sow/SOW.template.md" 2>/dev/null; then
291 + echo " ${GREEN}OK${NC} template includes open-source reference evidence"
292 + sow_template_open_source_reference_missing=0
293 + else
294 + echo " ${RED}--${NC} template missing open-source reference evidence"
295 + sow_template_open_source_reference_missing=1
296 + fi
297 + if grep -qF "directory name, not a status value" ".agents/sow/SOW.template.md" 2>/dev/null && grep -qF '`completed` is the successful terminal status' ".agents/sow/SOW.template.md" 2>/dev/null && grep -qF "one commit" ".agents/sow/SOW.template.md" 2>/dev/null; then
298 + echo " ${GREEN}OK${NC} template includes completed-status and one-commit close rule"
299 + sow_template_completion_rule_missing=0
300 + else
301 + echo " ${RED}--${NC} template missing completed-status or one-commit close rule"
302 + sow_template_completion_rule_missing=1
303 + fi
304 +>>>>>>> Stashed changes
305 else
306 echo " ${RED}--${NC} .agents/sow/SOW.template.md (missing)"
307 framework_missing=$((framework_missing + 1))
308 sow_template_pre_impl_missing=1
309 +<<<<<<< Updated upstream
310 +=======
311 + sow_template_sensitive_gate_missing=1
312 + sow_template_open_source_reference_missing=1
313 + sow_template_completion_rule_missing=1
314 +>>>>>>> Stashed changes
315 fi
316 if [ -f ".agents/sow/audit.sh" ]; then
317 echo " ${GREEN}OK${NC} .agents/sow/audit.sh"
@@ -230,6 +360,12 @@ for d in pending current done; do
360 esac
361 if $ok; then
362 echo " ${GREEN}OK${NC} $f ($status)"
363 + elif [ "$status" = "done" ]; then
364 + echo " ${RED}--${NC} $f (Status: done is invalid; use Status: completed in done/. done is the directory name, not a status)"
365 + sow_status_mismatch=$((sow_status_mismatch + 1))
366 + elif [ "$status" = "complete" ]; then
367 + echo " ${RED}--${NC} $f (Status: complete is invalid; use Status: completed in done/)"
368 + sow_status_mismatch=$((sow_status_mismatch + 1))
369 else
370 echo " ${RED}--${NC} $f (Status: $status does not match $d/)"
371 sow_status_mismatch=$((sow_status_mismatch + 1))
@@ -262,6 +398,97 @@ if [ "$current_sow_pre_impl_checked" -eq 0 ]; then
398 fi
399 echo
400
401 +<<<<<<< Updated upstream
402 +=======
403 +# --- Regression section placement ---
404 +echo "${BLUE}-- regression section placement --${NC}"
405 +regression_order_violations=0
406 +regression_order_checked=0
407 +for d in pending current done; do
408 + [ -d ".agents/sow/$d" ] || continue
409 + while IFS= read -r f; do
410 + [ -z "$f" ] && continue
411 + result=$(awk '
412 + /^## Regression([[:space:]-]|$)/ && !first_reg { first_reg = NR }
413 + /^## (Outcome|[Ll]essons [Ee]xtracted|Followup|Follow-up)$/ { if (NR > last_tail) last_tail = NR }
414 + END {
415 + if (!first_reg) {
416 + exit
417 + }
418 + if (last_tail && first_reg < last_tail) {
419 + print "bad:" first_reg ":" last_tail
420 + } else {
421 + print "ok:" first_reg ":" last_tail
422 + }
423 + }
424 + ' "$f")
425 + [ -z "$result" ] && continue
426 + regression_order_checked=$((regression_order_checked + 1))
427 + status=${result%%:*}
428 + details=${result#*:}
429 + if [ "$status" = "ok" ]; then
430 + echo " ${GREEN}OK${NC} $f (Regression section is appended)"
431 + else
432 + first_reg=${details%%:*}
433 + last_tail=${details#*:}
434 + echo " ${RED}--${NC} $f (Regression section starts at line $first_reg before original tail section ending at line $last_tail; append regressions to the end)"
435 + regression_order_violations=$((regression_order_violations + 1))
436 + fi
437 + done < <(find ".agents/sow/$d" -mindepth 1 -maxdepth 1 -name 'SOW-*.md' -type f 2>/dev/null | sort)
438 +done
439 +if [ "$regression_order_checked" -eq 0 ]; then
440 + echo " ${GRAY}(no regression sections found)${NC}"
441 +fi
442 +echo
443 +
444 +# --- Mirrored open-source reference evidence ---
445 +echo "${BLUE}-- mirrored open-source reference evidence --${NC}"
446 +mirror_path_violations=0
447 +mirror_path_checked=0
448 +for d in pending current done; do
449 + [ -d ".agents/sow/$d" ] || continue
450 + while IFS= read -r f; do
451 + [ -z "$f" ] && continue
452 + mirror_path_checked=$((mirror_path_checked + 1))
453 + if grep -qF "/opt/baddisk/monitoring/repos" "$f" 2>/dev/null; then
454 + echo " ${RED}--${NC} $f (uses /opt/baddisk/monitoring/repos absolute path; cite owner/repo @ commit plus repo-relative path)"
455 + mirror_path_violations=$((mirror_path_violations + 1))
456 + fi
457 + done < <(find ".agents/sow/$d" -mindepth 1 -maxdepth 1 -name 'SOW-*.md' -type f 2>/dev/null | sort)
458 +done
459 +if [ "$mirror_path_checked" -eq 0 ]; then
460 + echo " ${GRAY}(no SOW files found)${NC}"
461 +elif [ "$mirror_path_violations" -eq 0 ]; then
462 + echo " ${GREEN}OK${NC} checked $mirror_path_checked SOW file(s); mirrored repository evidence uses durable citations"
463 +fi
464 +echo
465 +
466 +# --- Sensitive data guardrail ---
467 +echo "${BLUE}-- sensitive data guardrail --${NC}"
468 +sensitive_findings=0
469 +sensitive_files_checked=0
470 +while IFS= read -r f; do
471 + [ -z "$f" ] && continue
472 + sensitive_files_checked=$((sensitive_files_checked + 1))
473 + scan_output=$(scan_sensitive_file "$f")
474 + if [ -n "$scan_output" ]; then
475 + while IFS= read -r finding; do
476 + [ -z "$finding" ] && continue
477 + echo " ${RED}--${NC} $finding"
478 + sensitive_findings=$((sensitive_findings + 1))
479 + done <<< "$scan_output"
480 + fi
481 +done < <(sensitive_scan_files | sort -u)
482 +if [ "$sensitive_files_checked" -eq 0 ]; then
483 + echo " ${GRAY}(no durable artifact files found)${NC}"
484 +elif [ "$sensitive_findings" -eq 0 ]; then
485 + echo " ${GREEN}OK${NC} scanned $sensitive_files_checked durable artifact file(s); no sensitive-data patterns found"
486 +else
487 + echo " ${RED}--${NC} $sensitive_findings sensitive-data pattern(s) found. Output is file:line:rule only; inspect locally and redact before commit."
488 +fi
489 +echo
490 +
491 +>>>>>>> Stashed changes
492 # --- Project skills ---
493 echo "${BLUE}-- runtime project skills --${NC}"
494 project_skills_ok=0
@@ -377,6 +604,13 @@ skill_classification_warnings=${non_project_skills_unclassified:-0}
604
605 sow_status_errors=$((sow_status_mismatch + sow_status_missing))
606 pre_impl_errors=$((sow_template_pre_impl_missing + current_sow_pre_impl_missing))
607 +<<<<<<< Updated upstream
608 +=======
609 +sensitive_gate_errors=$((sow_template_sensitive_gate_missing + current_sow_sensitive_gate_missing + sensitive_findings))
610 +open_source_reference_errors=${sow_template_open_source_reference_missing:-0}
611 +completion_rule_errors=${sow_template_completion_rule_missing:-0}
612 +sow_evidence_errors=$((regression_order_violations + mirror_path_violations + open_source_reference_errors + completion_rule_errors))
613 +>>>>>>> Stashed changes
614
615 if $initialized && [ "$sections_missing" -eq 0 ] && [ "$bridge_missing" -eq 0 ] && [ "$sow_dir_missing" -eq 0 ] && [ "$empty_sow_dir_missing_keep" -eq 0 ] && [ "$framework_missing" -eq 0 ] && [ "$sow_status_errors" -eq 0 ] && [ "$pre_impl_errors" -eq 0 ] && [ "$todo_untracked_count" -eq 0 ] && [ "$skill_classification_warnings" -eq 0 ]; then
616 echo " ${GREEN}=== SOW initialization complete and clean. ===${NC}"
@@ -398,6 +632,16 @@ elif $initialized; then
632 [ "$sow_status_missing" -gt 0 ] && echo " ${YELLOW}- ${sow_status_missing} SOW file(s) missing Status line${NC}"
633 [ "$sow_template_pre_impl_missing" -gt 0 ] && echo " ${YELLOW}- project-local SOW template missing Pre-Implementation Gate${NC}"
634 [ "$current_sow_pre_impl_missing" -gt 0 ] && echo " ${YELLOW}- ${current_sow_pre_impl_missing} current SOW(s) missing Pre-Implementation Gate${NC}"
635 +<<<<<<< Updated upstream
636 +=======
637 + [ "$sow_template_sensitive_gate_missing" -gt 0 ] && echo " ${YELLOW}- project-local SOW template missing sensitive data gates${NC}"
638 + [ "$current_sow_sensitive_gate_missing" -gt 0 ] && echo " ${YELLOW}- ${current_sow_sensitive_gate_missing} current SOW(s) missing sensitive data handling/gate${NC}"
639 + [ "$sensitive_findings" -gt 0 ] && echo " ${YELLOW}- ${sensitive_findings} sensitive-data finding(s) in durable artifacts${NC}"
640 + [ "${sow_template_open_source_reference_missing:-0}" -gt 0 ] && echo " ${YELLOW}- project-local SOW template missing open-source reference evidence field${NC}"
641 + [ "${sow_template_completion_rule_missing:-0}" -gt 0 ] && echo " ${YELLOW}- project-local SOW template missing completed-status or one-commit close rule${NC}"
642 + [ "$regression_order_violations" -gt 0 ] && echo " ${YELLOW}- ${regression_order_violations} SOW file(s) have regression sections before original outcome/lessons/follow-up${NC}"
643 + [ "$mirror_path_violations" -gt 0 ] && echo " ${YELLOW}- ${mirror_path_violations} SOW file(s) use /opt/baddisk/monitoring/repos absolute paths instead of owner/repo @ commit citations${NC}"
644 +>>>>>>> Stashed changes
645 [ "$todo_untracked_count" -gt 0 ] && echo " ${YELLOW}- ${todo_untracked_count} untracked orphan TODO file(s) at project root${NC}"
646 [ "$skill_classification_warnings" -gt 0 ] && echo " ${YELLOW}- ${skill_classification_warnings} non-project skill director(y/ies) need classification${NC}"
647 echo " ${YELLOW} Repair non-destructively using the project-local AGENTS.md and .agents/sow/SOW.template.md.${NC}"
.agents/sow/done/SOW-0001-20260502-project-writing-collectors-skill.md new
+387
@@ -0,0 +1,387 @@
1 +# SOW-0001 - project-writing-collectors skill
2 +
3 +## Status
4 +
5 +Status: completed
6 +
7 +Sub-state: skill shipped (SKILL.md 516 lines after structural rewrite). PR #22386 open against netdata/netdata master with three commits: pre-existing SOW lifecycle update (`64754ad4ea`), first-draft skill preserved as baseline (`9fdf581a86`, 313 lines, routing-and-pointers oriented), structural rewrite (`abe0b77ea2`, +395/-192, mental-model and data-type/domain centered). AGENTS.md skill index entry added in the first-draft commit. SOW close lands in a separate third commit per user-approved split from the rewrite.
8 +
9 +## Requirements
10 +
11 +### Purpose
12 +
13 +Create a runtime project skill `project-writing-collectors` that orients an AI assistant arriving cold at any Netdata data-collection task: tells the assistant *what canonical documents already exist*, *when to read each*, and *what is at stake* if collector authoring conventions are violated.
14 +
15 +The skill is a gateway. It must never duplicate canonical documentation. The repo already owns the deep references (NIDL framework, plugin frameworks, profile format, plugin protocol, DYNCFG, functions, streaming, integrations pipeline) — the skill points to them.
16 +
17 +The skill exists because, without it, every new collector task forces the user to manually re-teach: where docs live, what NIDL is, what frameworks v1/v2 are, what is mandatory in metadata.yaml / config_schema.json / health.d, that vnodes exist for remotely-monitored systems, that SNMP uses profiles, that no metric should default to zero, that hot paths must not log or allocate, etc. Each repetition is a tax on the user's time and a risk for incorrect output.
18 +
19 +### User Request
20 +
21 +Quoted from chat:
22 +
23 +> "I want us to work on a new skill: project-writing-collectors with description: best practices for Netdata collectors - read this before adding data collection plugins or modules to Netdata"
24 +
25 +> "let an assistant understand what it is dealing with"
26 +
27 +> "We need a balance. The assistants must get enough information to understand if and when they need to read additional documents, and grasp what is at stake when working with collectors. Best practices, Bad practices, Pointers for additional documentation."
28 +
29 +Constraints from the user:
30 +
31 +- General rules for ALL plugins (per-plugin rules go elsewhere — possibly per-plugin skills later).
32 +- Not an inventory (so go.d module names are out — categories only).
33 +- Not a comprehensive guide (the repo already has those).
34 +- The skill is *live* and may be updated when gaps are found, with user permission.
35 +
36 +### Assistant Understanding
37 +
38 +Facts:
39 +
40 +- The repo already owns substantial collector documentation: `docs/NIDL-Framework.md` (442 lines), `src/go/BEST-PRACTICES.md` (387), `src/go/COLLECTOR-LIFECYCLE.md` (1209), `src/plugins.d/README.md` (909), `src/plugins.d/DYNCFG.md` (468), `src/plugins.d/FUNCTION_UI_REFERENCE.md` (1714), `src/go/plugin/go.d/collector/snmp/profile-format.md` (2046), `src/go/plugin/ibm.d/framework/README.md` (153), plus a 56-line `src/go/plugin/go.d/docs/how-to-write-a-collector.md` for go.d.
41 +- A landing-page pattern already exists in the repo: `src/go/AGENTS.md` and `src/go/CLAUDE.md` (17 lines each, content-equal, scoped to IBM.D plugin) — short router with 5 rules. `src/go/plugin/ibm.d/AGENTS.md` (145 lines) is a deeper IBM.D checklist.
42 +- AGENTS.md mandates collector consistency between code, metadata.yaml, config_schema.json, stock conf, health.d, README.
43 +- ~30 plugins exist across C, Go, Rust, Python, Bash, eBPF.
44 +- 12 recurring bad-practice patterns identified with file:line evidence (see Analysis).
45 +- Existing project skills under `.agents/skills/` range from 162 (graphql-audit) to 508 (pr-reviews) lines; average ~330.
46 +- SOW directories were empty at SOW-creation time; this is SOW-0001 in numbering despite AGENTS.md text mentioning SOW-0003.
47 +
48 +Inferences:
49 +
50 +- A single SKILL.md is appropriate based on existing skill sizes and the orientation-only scope.
51 +- Updating-this-skill must be an explicit footer rule, since the user wants it to be live.
52 +- Audit rules and authoring rules largely overlap — splitting into two skills risks duplication; embedding both in one skill keeps the merge gate close to the authoring guidance.
53 +
54 +Unknowns:
55 +
56 +- Whether a separate `project-auditing-collectors` skill is desirable, or audit lives inside this skill (decision needed).
57 +- Whether to create companion docs in the skill dir for true gaps (vnodes, error handling, labels, logging conventions), or leave gaps as 1-2 line inline summaries plus follow-up SOWs (decision needed).
58 +
59 +### Acceptance Criteria
60 +
61 +- An assistant given a new collector task and only this skill must be able to:
62 + - identify which plugin tree the work belongs in;
63 + - locate every canonical doc relevant to that work;
64 + - know the non-negotiable rules (no zero defaults, no log spam, no per-iteration alloc/reconnect, no missing metadata.yaml/health.d, vnodes for remote, profiles for SNMP);
65 + - know the audit checklist before merge.
66 +- Verification: walk through 4 self-tests with the skill content visible:
67 + 1. New go.d module → routes to BEST-PRACTICES.md, COLLECTOR-LIFECYCLE.md, NIDL, how-to-write-a-collector.md, integrations/templates.
68 + 2. New SNMP profile → routes to profile-format.md.
69 + 3. New C external plugin → routes to plugins.d/README.md, src/collectors/README.md.
70 + 4. New ibm.d module → routes to ibm.d/framework/README.md, ibm.d/AGENTS.md.
71 +- Each of the 12 bad-practice patterns must be flagged either in the "non-negotiables" or "audit checklist" sections of the skill.
72 +- AGENTS.md "Project Skills Index" must list the new skill.
73 +
74 +## Analysis
75 +
76 +### Canonical Documents That Already Exist
77 +
78 +(verified to exist via Bash on 2026-05-02)
79 +
80 +| Topic | Path | Lines |
81 +|---|---|---|
82 +| NIDL framework | docs/NIDL-Framework.md | 442 |
83 +| go.d collector authoring (V2) | src/go/plugin/go.d/docs/how-to-write-a-collector.md | 56 |
84 +| go.d best practices | src/go/BEST-PRACTICES.md | 387 |
85 +| go.d collector lifecycle | src/go/COLLECTOR-LIFECYCLE.md | 1209 |
86 +| go.d plugin overview | src/go/plugin/go.d/README.md | (verified, lines unread) |
87 +| ibm.d framework | src/go/plugin/ibm.d/framework/README.md | 153 |
88 +| ibm.d landing/checklist | src/go/plugin/ibm.d/AGENTS.md | 145 |
89 +| ibm.d plugin overview | src/go/plugin/ibm.d/README.md | (verified) |
90 +| go.d/ibm.d landing | src/go/AGENTS.md = src/go/CLAUDE.md | 17 |
91 +| go.d agent framework | src/go/plugin/agent/README.md | (verified) |
92 +| External plugin protocol (PLUGINSD) | src/plugins.d/README.md | 909 |
93 +| Collector privileges/types | src/collectors/README.md | (verified) |
94 +| SNMP profile format | src/go/plugin/go.d/collector/snmp/profile-format.md | 2046 |
95 +| DYNCFG protocol | src/plugins.d/DYNCFG.md | 468 |
96 +| DYNCFG (developer corner) | docs/developer-and-contributor-corner/dyncfg.md | (verified) |
97 +| Functions reference | src/plugins.d/FUNCTION_UI_REFERENCE.md | 1714 |
98 +| Functions developer guide | src/plugins.d/FUNCTION_UI_DEVELOPER_GUIDE.md | (verified) |
99 +| Streaming & replication | src/streaming/README.md | (verified) |
100 +| Streaming parent clusters | src/streaming/PARENT-CLUSTERS.md | (verified) |
101 +| Health alerts (reference) | src/health/REFERENCE.md | (verified) |
102 +| Health alerts (overview) | src/health/README.md | (verified) |
103 +| Alert config ordering | src/health/alert-configuration-ordering.md | (verified) |
104 +| Overriding stock alerts | src/health/overriding-stock-alerts.md | (verified) |
105 +| Claim / registration | src/claim/README.md | (verified) |
106 +| Integrations pipeline | integrations/README.md | (verified) |
107 +| Integration templates | integrations/templates/README.md | (verified) |
108 +| Dynamic-configuration UI | docs/netdata-agent/configuration/dynamic-configuration.md | (verified) |
109 +| Replication of past samples | docs/observability-centralization-points/metrics-centralization-points/replication-of-past-samples.md | (verified) |
110 +| charts.d.plugin (legacy) | src/collectors/charts.d.plugin/README.md | (legacy) |
111 +| python.d.plugin (legacy) | src/collectors/python.d.plugin/README.md | (legacy) |
112 +
113 +### Documentation Gaps
114 +
115 +Topics where the repo lacks a canonical doc and the skill must either inline guidance or track a follow-up SOW:
116 +
117 +- Netdata labels (host/chart/instance) — no canonical reference.
118 +- Configuration override hierarchy (DYNCFG > /etc/netdata > stock > internal defaults) — scattered.
119 +- Memory allocation conventions (mallocz/freez/strdupz) — only in libnetdata code comments.
120 +- nd_log conventions (levels, throttling) — scattered across libnetdata.
121 +- Error handling conventions for collectors — none unified.
122 +- Vnode registration — code only, no public doc.
123 +- Chart/dimension definitions — examples in collectors only.
124 +- Plugin update interval / cadence guidance — scattered.
125 +- Testing patterns for collectors — no unified doc.
126 +- netipc library — canonical lives at github.com/netdata/plugin-ipc, not linked from this repo.
127 +
128 +### Recurring Bad Practices (file:line evidence)
129 +
130 +| # | Pattern | Example |
131 +|---|---|---|
132 +| 1 | Default-to-zero on missing data | src/collectors/proc.plugin/proc_net_dev.c:782 (TODO comment admits the bug) |
133 +| 2 | Log spam in iteration loops | ebpf.plugin (commit bde8262e33) |
134 +| 3 | Allocations in collection loop | src/go/plugin/go.d/collector/ap/collect.go:53 |
135 +| 4 | Reconnects per iteration | SNMP topology pre-hardening |
136 +| 5 | Vague error context | many go.d collectors return raw err with no wrap |
137 +| 6 | Silent fallbacks | src/go/plugin/go.d/collector/mysql/mysqlfunc/error_info.go (fallbackTable) |
138 +| 7 | Hardcoded options without DYNCFG | many SNMP/timeout defaults |
139 +| 8 | Missing vnode support | refactor commit 4245df367f |
140 +| 9 | Metrics shipped without metadata.yaml/health.d | 4 metadata gaps across 133 collectors |
141 +| 10 | Ignored syscall return codes | systemd-journal NULL guard commit b455bbe1c |
142 +| 11 | SNMP collectors without profiles | older topology code |
143 +| 12 | Blocking inside collection loop | apps.plugin commit 6084e3f98b |
144 +
145 +### External Pattern Reference
146 +
147 +- Telegraf input plugin guide (189 lines): minimal interface spec, convention over walkthrough.
148 +- OTel Collector CONTRIBUTING.md (470 lines): prescriptive PR shape, named audiences, defers RFC detail.
149 +- Datadog integrations README (58 lines): defers all detail to external docs site.
150 +- Prometheus exporter guides (~686 lines combined): hands-on, example-first.
151 +- Synthesis: lead with role/audience, links table, do/don't, defer deep detail.
152 +
153 +### Existing Project Skills (size reference)
154 +
155 +| Skill | Lines |
156 +|---|---|
157 +| graphql-audit | 162 |
158 +| sonarqube-audit | 190 |
159 +| coverity-audit | 474 |
160 +| pr-reviews | 508 |
161 +
162 +## Pre-Implementation Gate
163 +
164 +Status: needs-user-decision
165 +
166 +Problem / root-cause model:
167 +
168 +- AI assistants approaching Netdata data collection do not know which canonical docs exist (NIDL, frameworks, profiles, plugins.d protocol, DYNCFG, functions, streaming, integrations pipeline). Without orientation they: invent metric grouping, miss vnodes, log-spam in hot paths, default missing data to zero, miss metadata.yaml/health.d/config_schema, hardcode options, write SNMP without profiles, allocate per-iteration, reconnect per iteration, ignore syscall return codes. Evidence: 12 recurring patterns documented with file:line above. Without a router skill the user must reteach all of this in every new session.
169 +
170 +Evidence reviewed:
171 +
172 +- 25+ canonical in-repo docs verified to exist (Analysis section).
173 +- src/go/AGENTS.md / src/go/CLAUDE.md — existing 17-line landing page pattern, IBM.D-scoped.
174 +- src/go/plugin/go.d/docs/how-to-write-a-collector.md — go.d-specific 56-line guide.
175 +- 12 bad-practice patterns from subagent investigation.
176 +- External patterns from Telegraf, OTel, Datadog, Prometheus.
177 +- Existing project skills (sizing reference).
178 +
179 +Affected contracts and surfaces:
180 +
181 +- New file: `.agents/skills/project-writing-collectors/SKILL.md`.
182 +- AGENTS.md: add entry under "Runtime input project skills" in the Project Skills Index.
183 +- No code changes; no spec changes; no metadata.yaml/config_schema changes.
184 +- The skill will reference canonical docs by relative path; renames in the future require skill update.
185 +
186 +Existing patterns to reuse:
187 +
188 +- Frontmatter format from existing project skills (`name`, `description`, `type`).
189 +- Router shape from src/go/AGENTS.md (short, numbered rules, links table).
190 +- Length range from existing project skills (160-510 lines).
191 +- File:line evidence style from coverity-audit / sonarqube-audit (for the bad-practices section).
192 +
193 +Risk and blast radius:
194 +
195 +- Skill bloat: tries to cover everything; assistants stop reading. Mitigation: hard cap on length, defer all deep content to canonical docs.
196 +- Drift: if BEST-PRACTICES.md, NIDL-Framework.md, or profile-format.md change paths, skill links break. Mitigation: explicit footer rule that PRs touching collectors must update the skill if conventions or doc paths change.
197 +- Coverage illusion: a 12-item checklist does not guarantee an audit catches a bug. Mitigation: each bad-practice row carries file:line evidence so reviewers verify by example, not by checkbox.
198 +- Stale plugin landscape: plugins added/removed without updating skill. Mitigation: include the skill in the collector-consistency rule already in AGENTS.md.
199 +
200 +Sensitive data handling plan:
201 +
202 +- The skill ships in a public repository. It must contain no customer names, no private endpoints, no credentials, no internal tooling references. Bad-practice file:line evidence cites public source code only. No issue.
203 +
204 +Implementation plan:
205 +
206 +(Awaiting user decisions before finalizing.)
207 +
208 +1. Author SKILL.md based on the structure decided in "Open decisions".
209 +2. Register the skill in AGENTS.md → Project Skills Index → Runtime input skills.
210 +3. Self-test with the 4 routing scenarios listed in Acceptance Criteria.
211 +4. Run the 12 bad-practice patterns against the SKILL.md to confirm each is flagged.
212 +5. Open a follow-up SOW (or follow-ups, plural) for each canonical-doc gap that the user wants the skill to point at but no canonical doc yet exists.
213 +
214 +Validation plan:
215 +
216 +- 4 self-test routing walkthroughs (above).
217 +- 12-pattern coverage verification (above).
218 +- User review of structure before implementation begins.
219 +- After implementation, re-walkthrough with a fresh subagent that has not seen the design conversation: does it route correctly?
220 +
221 +Artifact impact plan:
222 +
223 +- AGENTS.md: add new skill entry under Runtime input project skills (single trigger line + 2-3 lines of "use when").
224 +- Runtime project skills: this SOW *creates* the skill.
225 +- Specs: no spec change expected (not a behavioral change).
226 +- End-user/operator docs: none affected.
227 +- End-user/operator skills: none affected.
228 +- SOW lifecycle: this SOW transitions pending→current after decisions, current→done on completion. Gap follow-ups (vnodes doc, error-handling doc, labels doc, logging conventions doc, etc.) tracked as separate SOWs in pending/.
229 +
230 +Open decisions: resolved 2026-05-02.
231 +
232 +1. Single skill, audit checklist embedded (1A).
233 +2. Full plugin coverage in v1 (2A).
234 +3. Gap topics handled inline as 1-2 line guidance with pointers; canonical doc gaps tracked as follow-up SOWs (3A).
235 +4. Plugin landscape embedded in SKILL.md (4A).
236 +5. Legacy plugins (charts.d, python.d) included with "do not add new modules" marker (5A).
237 +6. Bad-practice file:line evidence kept as nudges, not enforcement (6A); reframed away from "audit gate" toward "past pain looked like this".
238 +7. Length target 300-500 lines; manifesto framing (prose, trust the reader, no MUST/MANDATORY/NEVER), summary DOs and DON'Ts per topic, no lengthy code examples. Draft came in at 242 lines — lean by design, will expand on user request if specific sections feel thin.
239 +8. Skill description tightened to "best practices + orientation" framing (8B), with broad trigger keywords for collector/plugin/module/integration/data-collection work.
240 +
241 +Late addition by user: a function schema JSON file (`src/plugins.d/FUNCTION_UI_SCHEMA.json`) is the contract for any collector that exposes a function. Added to the consistency-sync set in the Documentation section of the skill, and called out in the Functions topic with pointers to FUNCTION_UI_DEVELOPER_GUIDE.md and FUNCTION_UI_REFERENCE.md.
242 +
243 +## Implications And Decisions
244 +
245 +All open decisions (1-8) resolved 2026-05-02 — see Pre-Implementation Gate "Open decisions" section for the resolved set.
246 +
247 +User audit on the first draft prompted a comprehensive rewrite that re-centered the skill on:
248 +
249 +- mental model first (research discipline, cross-project comparison, gaps-are-data, obsoletion as a truthfulness principle, IDs as contracts);
250 +- framework-agnostic best practices ordered by impact;
251 +- five dashboard-shaping mechanisms (NIDL, SNMP profiles, statsd `synthetic_charts`, OTEL per-metric YAML mappings, Prometheus deterministic exposition);
252 +- production-quality criteria + 21-item pre-PR checklist;
253 +- plugin landscape demoted to reference;
254 +- per-data-type chapter (metrics, logs, live snapshots, topology, netipc enrichment);
255 +- per-domain common practices (DBs + query Functions, network/SNMP + topology Functions, containers + netipc enrichment, web servers + access-log Functions, flow protocols).
256 +
257 +Two corrections during review:
258 +
259 +- A comparative claim about Netdata's per-series cost (vs. other monitoring systems) was caught and removed before commit. Such comparisons are out of scope for an in-repo skill whose audience is assistants working on the codebase.
260 +- Obsoletion was originally bundled under cardinality. Separated: obsoletion is now §1.5 (truthfulness principle, applies at any cardinality) and cardinality bounding is §2.5 (`max_*` + selectors mandatory, with three upstream-data-shape sub-cases).
261 +
262 +## Plan
263 +
264 +Plan executed:
265 +
266 +1. Authored first draft per resolved decisions (1A/2A/3A/4A/5A/6A/8B + 7 manifesto framing). Committed as `9fdf581a86`.
267 +2. Registered skill in AGENTS.md → Project Skills Index → Runtime input skills (same commit).
268 +3. User audit identified structural imbalance (over-indexed on entry-points). Restructured into 9 sections, with mental model and best practices leading and the framework reference demoted.
269 +4. Three parallel research subagents fetched OTEL / statsd / Prometheus mapping references with file:line evidence — grounded the dashboard-shaping section in source rather than prior knowledge.
270 +5. Domain-pattern verification (mysql `mysqlfunc/top_queries.go`, postgres `func_top_queries.go` / `func_router.go`, snmp_topology `func_topology*.go`, cgroups netipc server, log2journal) confirmed common-practices descriptions before writing.
271 +6. Two follow-up corrections during user review: removed unfair comparative claim; separated obsoletion from cardinality bounding.
272 +7. Rewrite committed as `abe0b77ea2`. PR #22386 opened against netdata/netdata master.
273 +
274 +## Execution Log
275 +
276 +### 2026-05-02 — investigation
277 +
278 +- Investigation completed via 4 parallel subagents (in-repo doc inventory, plugin landscape, recurring bad-practice patterns, external authoring-guide structures).
279 +- 25+ canonical docs verified to exist on disk.
280 +- Critical missed doc found: `src/go/plugin/go.d/docs/how-to-write-a-collector.md` (subagent A miss, recovered by direct grep).
281 +- 12 bad-practice patterns collected with file:line evidence.
282 +- 4 external project authoring guides analyzed for structure inspiration.
283 +- SOW filed; awaiting structure decisions.
284 +
285 +### 2026-05-02 — implementation and rewrite
286 +
287 +- First draft authored per resolved decisions; 313 lines; committed as `9fdf581a86`.
288 +- AGENTS.md updated with `project-writing-collectors` entry under Runtime input project skills.
289 +- User audit identified structural imbalance (skill over-indexed on entry-points, under-indexed on holistic data-collection thinking).
290 +- Three parallel research subagents fetched OTEL / statsd / Prometheus mapping references — used to ground the §3 dashboard-shaping section in real file:line evidence rather than prior knowledge.
291 +- Domain-pattern verification (mysql, postgres, snmp_topology, cgroups, log2journal) confirmed common-practices descriptions before writing §7.
292 +- Rewrite committed as `abe0b77ea2` (516 lines, +395 / -192 vs first draft).
293 +- User review caught and addressed: (a) a comparative-against-Netdata claim in §1.9 — removed; (b) obsoletion mixed into cardinality bounding — separated, promoted to §1.5 as a truthfulness principle.
294 +- PR #22386 opened against netdata/netdata master.
295 +- SOW close lands in a separate third commit per user-approved split.
296 +
297 +## Validation
298 +
299 +**Acceptance criteria evidence.** All four routing self-tests from §Acceptance Criteria are satisfied by the rewrite:
300 +
301 +1. New go.d module → §5.2 routing-by-task table + §5.3 V1/V2 reality check + ping V2 reference + `how-to-write-a-collector.md` pointer.
302 +2. New SNMP profile → §3.2 SNMP profiles + `profile-format.md` pointer.
303 +3. New external C plugin → §5.2 routing + `plugins.d/README.md` + §5.4 internal-C/PLUGINSD section.
304 +4. New ibm.d module → §5.2 routing + §5.4 ibm.d entry + `ibm.d/AGENTS.md` pointer.
305 +
306 +Each of the 12 bad-practice patterns from §Analysis is flagged in the rewrite, distributed across §1 (mental model), §2 (best practices), §3 (dashboard shaping), §4 (production-quality criteria + checklist) — reframed as past-pain context rather than a single audit-checklist column.
307 +
308 +**Tests / equivalent validation.** The skill is documentation, not code — validated by content review and routing walkthrough.
309 +
310 +**Real-use evidence.** Skill description triggers on the keywords specified in the user's request (collector, plugin, module, NetFlow/sFlow/IPFIX, OTEL, topology, SNMP profile, statsd, Prometheus scraping, Functions). Frontmatter description updated in the rewrite commit to enumerate all five dashboard-shaping mechanisms so discovery covers the full surface.
311 +
312 +**Reviewer findings and how handled:**
313 +
314 +- Structural imbalance after the first draft → addressed by full rewrite (Execution Log).
315 +- Unfair comparative claim about Netdata vs. other monitoring systems in §1.9 → removed; reframed in operational-waste terms.
316 +- Obsoletion conceptually misplaced under cardinality → separated; obsoletion is §1.5 (truthfulness, any cardinality), cardinality bounding is §2.5 (`max_*` + selectors + upstream-data-shape sub-cases).
317 +- `max_*` + selectors must be coupled, with three upstream-data-shape sub-cases ("Other" bucket / push selector upstream / surface app-side aggregations) → added to §2.5.
318 +
319 +**Same-failure search.** Reviewed the rewrite for other comparative claims about Netdata vs. alternatives — only §1.9 had the bad framing. Other sections that mention third-party projects (§1.6 cross-project comparison, §1.7 spec ambiguity, §2.1 testing, §3.5 Prometheus mapping) describe them neutrally as fixture sources or upstream-shape examples.
320 +
321 +**Artifact maintenance gate:**
322 +
323 +- AGENTS.md → updated (skill index entry under Runtime input project skills; commit `9fdf581a86`).
324 +- Runtime project skills → created (the new skill is the artifact).
325 +- Specs → no spec change. Skill is orientation; it does not change collector behavior, public APIs, schemas, alerting semantics, or operational guarantees.
326 +- End-user / operator docs → none affected. Audience is AI assistants, not end users or operators.
327 +- End-user / operator skills → none affected. The skill is a runtime input skill; it does not feed into output/reference skills under `docs/netdata-ai/skills/` or `src/ai-skills/`.
328 +- SOW lifecycle → this commit closes the SOW (status `completed`) and moves it to `.agents/sow/done/`. PR #22386 ends up with three commits (SOW lifecycle update + first-draft skill + rewrite) plus this lifecycle close.
329 +
330 +**SOW status / directory consistency.** Status `completed` → file moves to `.agents/sow/done/`.
331 +
332 +**Spec update or specific reason no spec update was needed.** Not needed — see Artifact maintenance gate.
333 +
334 +**Project skill update or specific reason no skill update was needed.** This SOW *creates* the project skill; AGENTS.md skill index updated.
335 +
336 +**End-user/operator docs update or evidence-backed reason none affected.** None affected — see Artifact maintenance gate.
337 +
338 +**End-user/operator skill update or evidence-backed reason none affected.** None affected — see Artifact maintenance gate.
339 +
340 +**Lessons extracted.** See Lessons Extracted section below.
341 +
342 +**Follow-up mapping.** See Followup section below.
343 +
344 +## Outcome
345 +
346 +Skill shipped at `.agents/skills/project-writing-collectors/SKILL.md` (516 lines). Indexed in AGENTS.md. PR #22386 open against netdata/netdata master.
347 +
348 +Coverage:
349 +
350 +- Mental model: 11 numbered principles.
351 +- Best practices: 10 directives ordered by impact.
352 +- Dashboard shaping: 6 mechanisms (NIDL, SNMP profiles, statsd `synthetic_charts`, OTEL mappings, Prometheus exposition, chart priorities).
353 +- Production-quality criteria: 7 + 21-item pre-PR checklist.
354 +- Plugin landscape: 18 plugin families.
355 +- Data types: 5 (metrics, logs, live snapshots, topology, netipc enrichment).
356 +- Common practices: 7 collector domains.
357 +- Canonical pointers: 30 entries.
358 +
359 +## Lessons Extracted
360 +
361 +1. **Comparative claims about Netdata are out of scope for in-repo skills.** A statement framing Netdata's per-series cost as worse than alternatives was both factually wrong (Netdata is more efficient on cardinality, with automated protection built in) and rhetorically inappropriate for an internal skill whose audience is assistants working on the codebase. Future skill content must teach assistants to design well, not position Netdata against other systems — in either direction.
362 +
363 +2. **Obsoletion is a truthfulness concern, not a cardinality concern.** The first-draft framing bundled them, which suggests obsoletion is only relevant at high cardinality. The principle applies even at one entity total: when the collector knows an entity is gone, the dashboard must reflect that. Future skill or doc content must keep these separate.
364 +
365 +3. **`max_*` and selectors must be coupled.** A cap alone silently truncates the wrong set; selectors alone don't protect against runaway. The skill must teach this as a combined directive, not two independent options.
366 +
367 +4. **Where to filter depends on what the application exposes.** Three upstream cases must be distinguished: app exposes everything (collector caps + adds an "Other" aggregation), app supports cherry-picking (push selector upstream), app exposes aggregations natively (surface them as additional charts). Without this guidance assistants default to "cap and drop", which loses information.
368 +
369 +5. **Research before describing internal mechanics.** The §3 dashboard-shaping section was grounded in real file:line evidence from the OTEL plugin, statsd plugin, and Prometheus collector — not prior knowledge. This avoided several plausible-sounding but inaccurate claims (e.g. about OTel semantic-convention handling).
370 +
371 +6. **Preserve first drafts when a substantial rewrite follows.** Committing the first draft separately let the rewrite stand on its own as a reviewable change (+395 / -192) and made the structural shift visible in history. Future SKILL-level rewrites should follow the same pattern.
372 +
373 +7. **The SOW close should land with the work as one commit.** This SOW shipped its work in two commits but was left in `current/` until the user noticed. The AGENTS.md rule ("commit the work, artifact updates, SOW status change, and SOW move together as one commit") exists to prevent exactly this. Future SOWs must close in the same commit as the final piece of work, unless the user explicitly approves a split (as happened here for SOW-0001).
374 +
375 +## Followup
376 +
377 +Per the resolved Open Decision 3A, canonical-doc gaps are handled inline as 1-2 line guidance in the skill rather than companion docs in the skill directory. The skill currently inlines guidance for:
378 +
379 +- Vnodes (§1.9, §2.10) → pointer to `src/go/plugin/framework/vnodes/` and `BEST-PRACTICES.md`.
380 +- Error handling conventions (§2.3) → three-question format directive.
381 +- Logging conventions (§2.4) → debug/warn/error/info hierarchy directive.
382 +- Labels (§3 dashboard shaping) → described per ingestion path (NIDL, SNMP, statsd, OTEL, Prometheus).
383 +- netipc (§2.9, §6.5) → pointer to upstream spec at <https://github.com/netdata/plugin-ipc>.
384 +
385 +No follow-up SOWs are required at this time. The skill is live; if assistants miss any inlined topic in practice, the appropriate response is to expand the relevant section in a follow-up commit per the "Maintaining this skill" footer rule, not to author a separate canonical doc.
386 +
387 +Maintenance handle: when a new gap is identified (recurring AI bug pattern from real PRs, documentation drift caused by a rename in canonical docs, new collector domain), open a follow-up SOW only if the change is substantial; otherwise update the skill in the same PR that exposed the gap.
AGENTS.md
+16 -1
@@ -102,6 +102,19 @@ Status and directory must agree:
102 - `completed` lives in `done/`
103 - `closed` lives in `done/`
104
105 +### SOW Completion And Commit
106 +
107 +The successful terminal SOW status is `completed`. `done` is a directory name, not a status value. Never write `Status: done` or `Status: complete`.
108 +
109 +When a SOW's work is ready to close:
110 +
111 +1. Finish implementation, docs, specs, skills, validation, and follow-up mapping.
112 +2. Update the SOW to `Status: completed`.
113 +3. Move the SOW file to `.agents/sow/done/`.
114 +4. Commit the work, artifact updates, SOW status change, and SOW move together as one commit, unless the user explicitly requested a different commit split.
115 +
116 +Do not create a separate commit just to mark or move the SOW. Do not claim a SOW is completed while the implementation and the SOW lifecycle change live in separate uncommitted or separately committed states.
117 +
118 ### One SOW At A Time
119
120 Never execute multiple SOWs as one batch.
@@ -222,7 +235,9 @@ Output/reference skills may also exist under product documentation or generated
235
236 Runtime input skills:
237
225 -- None yet under `.agents/skills/project-*/`. The user requested incremental creation instead of bootstrap-generated project skills.
238 +- `.agents/skills/project-writing-collectors/`
239 + Trigger: authoring or modifying any Netdata data-collection plugin or module (Go go.d / ibm.d, Rust crates, internal C plugins, external plugins via PLUGINSD). Read before adding a new collector, modifying an existing one, working on NetFlow/sFlow/IPFIX, OTEL ingestion, topology, SNMP profiles, or interactive Functions.
240 + Status: live. Updates that close gaps or fix outdated pointers must ship in the same PR that exposed the issue.
241
242 Legacy runtime skills:
243