Files
mfd-monitor-agent/docs/superpowers/plans/2026-05-18-mfd-monitor-agent-completion.md
codex 89d5e27874 💥 feat(agent)!: 添加主机监控代理首版
* 实现周期采集、快照缓存、本地 HTTP API 和主动上报

* 补齐 Linux 和 Windows 主机、网络、资源、进程和 TCP 采集

* 添加服务生命周期管理、配置加载、构建脚本和测试覆盖

BREAKING CHANGE: network.ips 拆分为 network.ipv4s 和 network.ipv6s。
2026-05-18 15:25:51 +00:00

12 KiB

MFD Monitor Agent Completion Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [x]) syntax for tracking.

Goal: Complete the host monitoring agent behavior required by AGENTS.md while preserving the current single-binary, cached-snapshot architecture.

Architecture: Keep the current packages and add test seams only where needed. Linux collectors are completed with parser-level tests, service installation becomes testable through unit rendering and command injection, reporter retry/backoff stays isolated from HTTP and collection, and Windows service behavior remains build-tagged.

Tech Stack: Go 1.22, standard library HTTP/config/process APIs, Linux /proc, systemd, Windows Service build tags, go test.


File Structure

  • Modify cmd/agent/main.go: preserve config path for service installation and add comments.
  • Modify internal/status/types.go: add required comments for API-visible JSON model fields and types.
  • Modify internal/config/config.go: add comments and preserve existing defaults/env behavior.
  • Modify internal/snapshot/snapshot.go: add comments and tests for timeout/partial behavior.
  • Modify internal/httpapi/server.go: add comments and tests for not-ready/method behavior.
  • Modify internal/reporter/reporter.go: add retry/backoff and tests.
  • Modify internal/collector/collector.go: add comments.
  • Modify internal/collector/collector_linux.go: complete Linux CPU/process/TCP partial behavior and add parser seams.
  • Modify internal/collector/collector_other.go: add comments and explicit unsupported behavior.
  • Create internal/collector/collector_linux_test.go: parser and degradation tests.
  • Modify internal/service/service.go: add comments and extend install options if needed.
  • Modify internal/service/service_linux.go: root guard, systemd unit rendering, config path propagation, command seams.
  • Create internal/service/service_linux_test.go: root guard and unit rendering tests.
  • Modify internal/service/service_windows.go: document Windows service behavior and improve no-NSSM path where compile-testable.
  • Modify internal/service/service_other.go: comments.

Task 1: Comment Coverage Baseline

Files:

  • Modify: internal/status/types.go

  • Modify: internal/config/config.go

  • Modify: internal/snapshot/snapshot.go

  • Modify: internal/httpapi/server.go

  • Modify: internal/reporter/reporter.go

  • Modify: internal/collector/collector.go

  • Modify: internal/collector/collector_linux.go

  • Modify: internal/collector/collector_other.go

  • Modify: internal/service/service.go

  • Modify: internal/service/service_linux.go

  • Modify: internal/service/service_windows.go

  • Modify: internal/service/service_other.go

  • Step 1: Run comment lint proxy

Run: go test ./...

Expected: PASS. This is the behavioral baseline before comments.

  • Step 2: Add comments

Add concise comments above every package-level type, struct, interface, function, and method. For API structs in status, include JSON/API meaning and units where relevant, for example:

// HostStatus is the full cached host snapshot returned by /status and posted by the reporter.
type HostStatus struct {
	// Agent contains agent process metadata and the latest snapshot status.
	Agent AgentInfo `json:"agent"`
}
  • Step 3: Verify comments did not change behavior

Run: go test ./...

Expected: PASS.

Task 2: HTTP Cache Edge Tests

Files:

  • Modify: internal/httpapi/server_test.go

  • Modify: internal/httpapi/server.go

  • Step 1: Write failing tests

Add tests named:

func TestStatusReturnsServiceUnavailableBeforeSnapshot(t *testing.T) {}
func TestHealthReturnsStartingBeforeSnapshot(t *testing.T) {}
func TestStatusRejectsNonGET(t *testing.T) {}
func TestHealthRejectsNonGET(t *testing.T) {}

Assert /status returns 503 when StaticProvider{OK:false}, /health returns JSON status starting, and POST requests return 405.

  • Step 2: Verify red or existing green

Run: go test ./internal/httpapi -run 'Test(StatusReturnsServiceUnavailableBeforeSnapshot|HealthReturnsStartingBeforeSnapshot|StatusRejectsNonGET|HealthRejectsNonGET)'

Expected: PASS if behavior already exists, otherwise FAIL with the missing behavior.

  • Step 3: Implement only missing behavior

Keep handler behavior cache-only. Do not add live collection.

  • Step 4: Verify

Run: go test ./internal/httpapi

Expected: PASS.

Task 3: Reporter Retry And Cancellation

Files:

  • Modify: internal/reporter/reporter.go

  • Modify: internal/reporter/reporter_test.go

  • Step 1: Write failing tests

Add tests named:

func TestReportReturnsErrorForHTTPFailure(t *testing.T) {}
func TestRunRetriesFailedReportOnce(t *testing.T) {}
func TestRunStopsDuringRetryDelayWhenContextCanceled(t *testing.T) {}

Use httptest.Server and a short interval/delay. Count requests with an atomic counter. First request returns 500, second returns 202.

  • Step 2: Verify red

Run: go test ./internal/reporter -run 'Test(ReportReturnsErrorForHTTPFailure|RunRetriesFailedReportOnce|RunStopsDuringRetryDelayWhenContextCanceled)'

Expected: retry tests FAIL because Run does not retry yet.

  • Step 3: Implement minimal retry

Add a retry delay parameter through an internal helper while preserving the public Run signature:

const defaultRetryDelay = 2 * time.Second

func Run(ctx context.Context, provider Provider, client *Client, interval time.Duration, logger *slog.Logger) {
	runWithRetryDelay(ctx, provider, client, interval, defaultRetryDelay, logger)
}

In runWithRetryDelay, after a failed report, wait for retryDelay or context cancellation, then retry once.

  • Step 4: Verify

Run: go test ./internal/reporter

Expected: PASS.

Task 4: Linux Collector Parser Coverage

Files:

  • Create: internal/collector/collector_linux_test.go

  • Modify: internal/collector/collector_linux.go

  • Step 1: Write parser tests

Add tests named:

func TestParseAddressIPv4(t *testing.T) {}
func TestParseAddressIPv6(t *testing.T) {}
func TestParseProcessStatStartTime(t *testing.T) {}
func TestCPUUsageFromProcStatSamples(t *testing.T) {}

Use sample strings from /proc/net/tcp, /proc/net/tcp6, /proc/<pid>/stat, and /proc/stat. Expected IPv4 sample 0100007F:1F90 maps to 127.0.0.1:8080.

  • Step 2: Verify red

Run: go test ./internal/collector -run 'Test(ParseAddress|ParseProcessStatStartTime|CPUUsageFromProcStatSamples)'

Expected: new process stat and CPU usage parser tests FAIL until helpers exist.

  • Step 3: Implement parser helpers

Add helpers:

func parseProcessStatStartTime(raw string) (int64, error)
func parseCPUTimes(raw string) (idle uint64, total uint64, err error)
func cpuUsagePercent(prevIdle, prevTotal, nextIdle, nextTotal uint64) float64

Keep helpers unexported and deterministic.

  • Step 4: Verify

Run: go test ./internal/collector

Expected: PASS.

Task 5: Linux Collection Fields

Files:

  • Modify: internal/collector/collector_linux.go

  • Modify: internal/collector/collector_linux_test.go

  • Step 1: Write failing tests

Add tests for reading process start time from a temporary stat file through a helper:

func TestReadProcessStatReturnsStartTime(t *testing.T) {}

Add a CPU usage test around the helper rather than sleeping on real /proc.

  • Step 2: Verify red

Run: go test ./internal/collector -run 'Test(ReadProcessStatReturnsStartTime|CPUUsage)'

Expected: FAIL until readProcess and collectResource use the helpers.

  • Step 3: Implement collection

Update readProcess to set StartTime. Update collectResource to set CPU.UsagePercent through a bounded sample that respects context:

select {
case <-ctx.Done():
	return ctx.Err()
case <-time.After(100 * time.Millisecond):
}
  • Step 4: Verify

Run: go test ./internal/collector

Expected: PASS.

Task 6: TCP Listener Partial Mapping

Files:

  • Modify: internal/collector/collector_linux.go

  • Modify: internal/collector/collector_linux_test.go

  • Step 1: Write failing test

Add:

func TestCollectSocketsMarksPartialWhenProcessFDsUnreadable(t *testing.T) {}

Use a helper seam around socket inode walking so the test can simulate an unreadable fd directory.

  • Step 2: Verify red

Run: go test ./internal/collector -run TestCollectSocketsMarksPartialWhenProcessFDsUnreadable

Expected: FAIL because unreadable fd directories are currently ignored.

  • Step 3: Implement partial error propagation

Make inode collection return (map[string]status.ProcessInfo, error). Keep best-effort mappings, but return an error count when pid fd directories cannot be read. collectSockets sets tcp_listeners.status to partial and returns the error while preserving listener items.

  • Step 4: Verify

Run: go test ./internal/collector

Expected: PASS.

Task 7: Linux Service Install Guard And Unit Rendering

Files:

  • Modify: cmd/agent/main.go

  • Modify: internal/service/service.go

  • Modify: internal/service/service_linux.go

  • Create: internal/service/service_linux_test.go

  • Step 1: Write failing tests

Add:

func TestRenderSystemdUnitIncludesConfigPath(t *testing.T) {}
func TestLinuxInstallRequiresRoot(t *testing.T) {}

Assert the rendered unit includes ExecStart=/opt/agent run -config /etc/mfd-agent.json when a config path is supplied.

  • Step 2: Verify red

Run: go test ./internal/service -run 'Test(RenderSystemdUnitIncludesConfigPath|LinuxInstallRequiresRoot)'

Expected: FAIL because config path and root guard seams do not exist.

  • Step 3: Implement install options

Add a small options struct or extend Install input so cmd/agent can pass the config path. Add root guard through an injectable currentUID function and render the unit through a pure helper:

func renderSystemdUnit(executable string, configPath string, cfg config.Config) string
  • Step 4: Verify

Run: go test ./internal/service

Expected: PASS.

Task 8: Snapshot Timeout And Partial Tests

Files:

  • Modify: internal/snapshot/snapshot_test.go

  • Modify: internal/snapshot/snapshot.go

  • Step 1: Write tests

Add:

func TestRunCollectsImmediately(t *testing.T) {}
func TestCollectWithTimeoutMarksCanceledCollectorPartial(t *testing.T) {}

Use a collector that waits on ctx.Done() and returns ctx.Err().

  • Step 2: Verify

Run: go test ./internal/snapshot

Expected: PASS if current timeout behavior already satisfies this; otherwise FAIL and then implement the minimal fix.

Task 9: Windows Build-Tagged Service Contracts

Files:

  • Modify: internal/service/service_windows.go

  • Optional create: internal/service/service_windows_test.go

  • Step 1: Add comments and compile-safe structure

Ensure Windows service code is build-tagged, documented, and does not reference Linux-only APIs.

  • Step 2: Compile-check Windows

Run: GOOS=windows GOARCH=amd64 go test ./...

Expected: PASS. If network/dependency constraints block a wrapper dependency, document the exact failure and keep Windows behavior explicit.

Task 10: Final Verification And Audit

Files:

  • Inspect: AGENTS.md

  • Inspect: all modified Go files

  • Step 1: Run full tests

Run: go test ./...

Expected: PASS.

  • Step 2: Run Windows compile-check

Run: GOOS=windows GOARCH=amd64 go test ./...

Expected: PASS or documented blocker.

  • Step 3: Audit comments

Run: grep -R "^func \\|^type " -n cmd internal

Expected: every listed package-level function/type has a preceding useful comment.

  • Step 4: Completion checklist

Map each explicit AGENTS.md requirement to implementation evidence, test evidence, or a documented first-version limitation.

Git Note

This directory is not currently a Git repository, so plan steps cannot include real commits. Use the test checkpoints above as completion gates.