Skip to content

feat(lint): add dead-code lint for flags and unexported helpers#198

Merged
ktn-jamf merged 2 commits into
mainfrom
feat/lint-dead-code
May 11, 2026
Merged

feat(lint): add dead-code lint for flags and unexported helpers#198
ktn-jamf merged 2 commits into
mainfrom
feat/lint-dead-code

Conversation

@ktn-jamf
Copy link
Copy Markdown
Collaborator

Summary

  • New standalone tool at scripts/lint-dead-code/ that AST-walks internal/commands/ and surfaces dead Cobra flag bindings + dead unexported helpers.
  • Allowlist via Cobra Annotations["lint:keep-flag"] for flags and //lint:keep doc comments for funcs.
  • Wired as a CI step (make lint-dead) with continue-on-error: true — warn-only for the first 2 weeks.
  • Flips to exit-2 with --gate once we've burned in the heuristics.

Why

Inspired by cli-printing-press's dogfood structural verifier. As the
generator's surface grew, hand-written commands accumulate hidden tax:
flag variables that lost their last reader during a refactor, helpers
that no other path calls anymore. At our scale (~373 flag bindings in
internal/commands/, dozens of unexported helpers), grep+eyeball is too
unreliable to catch this on every PR. A 200-line AST walker run on every
push gives us mechanical confidence — and the same allowlist convention
(lint:keep-flag annotations, //lint:keep doc comments) generalises
to future structural verifiers (anti-reimplementation, MCP-surface-parity,
naming-consistency).

Behavior contract

Dead flags: call sites matching <cmd>.{Persistent}Flags().XVar(&backing, "name", ...) where the backing ident has zero reads anywhere in the same package besides the binding &IDENT and the declaration site. Scans test files too — test reads count as legitimate use.

Dead funcs: top-level unexported funcs in internal/commands/ (recursive, including pro/generated/ and platform/generated/) with zero same-package references besides the declaration. Skips init, main, Test*, Benchmark*, Fuzz*, Example*, and methods.

Allowlist (flags): add Annotations: map[string]string{"lint:keep-flag": "name1,name2"} to the owning *cobra.Command composite literal. Statically resolved on a best-effort basis — if the literal isn't statically resolvable, no allowlist applies.

Allowlist (funcs): add //lint:keep to the func's doc-comment group. Bare marker accepted for now; rationale convention may be enforced later.

First-sweep findings

$ go run ./scripts/lint-dead-code/
No dead flags or functions found.

Zero — the codebase is in good shape. The infrastructure is in place so we catch the next drift, not the current state. Will revisit if maintainers spot false negatives.

Test plan

  • go build ./... passes
  • go test ./... passes (new scanner tests + full suite)
  • make lint clean (golangci-lint)
  • make verify-generated passes
  • make lint-dead runs cleanly (no findings)
  • Four testdata fixtures (clean, dead-flag, dead-func, allowlisted) cover the positive + suppression paths

Warn-only for the first 2 weeks. After calibration we'll flip the CI step to make lint-dead -- --gate (exit-2 on findings) and drop the continue-on-error.

🤖 Generated with Claude Code

Inspired by cli-printing-press's dogfood structural verifier. New
tool at scripts/lint-dead-code/ walks the Cobra command tree via AST,
identifies flag bindings whose backing var is never read, and finds
unexported helpers with zero callers.

Allowlist via Cobra Annotations["lint:keep-flag"] for flags and
//lint:keep doc comments for functions. Same convention can be
reused by future structural verifiers (anti-reimplementation,
MCP-surface-parity, naming-consistency).

Warn-only for the first 2 weeks: prints findings, exits 0. Promote
to exit-2 gating with --gate after a calibration window. CI step
added but does not gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ktn-jamf ktn-jamf merged commit 0082004 into main May 11, 2026
1 check passed
@ktn-jamf ktn-jamf deleted the feat/lint-dead-code branch May 11, 2026 16:17
ktn-jamf added a commit that referenced this pull request May 11, 2026
* docs: add GLOSSARY.md + docs/solutions/ archive

Two artifacts inspired by cli-printing-press, designed to compound over time
as institutional memory that future contributors (human and AI) can grep
before rediscovering the same patterns.

**docs/GLOSSARY.md** — canonical terms for jamf-cli's overlapping vocabulary:
Pro vs Platform vs Classic; blueprint vs config profile; smart vs static
group; scope vs target; MDM command vs declaration; ADE vs DEP; etc. Closes
the "which 'profile' / 'group' / 'policy' do you mean?" ambiguity that
costs a round-trip in every AI conversation.

**docs/solutions/** — categorized markdown postmortems with YAML frontmatter
(title, date, category, module, problem_type, severity, applies_when, tags).
Five category subdirs match cli-printing-press: best-practices, conventions,
design-patterns, logic-errors, security-issues. Two seed entries:

- conventions/output-flag-matrix-2026-05-08.md — exercise -o json/yaml/csv,
  --quiet, --no-color, --out-file for every new command before declaring
  done. Captures the lessons from #194 (spinner+NO_COLOR) and #195
  (version -v -o json).

- design-patterns/cobra-annotations-as-policy-2026-05-11.md — prefer Cobra
  Annotations for per-command policy metadata (lint:keep-flag, future
  mcp:hidden, jamf:destructive) over comment magic, separate registries,
  or per-tool allowlists. Captures the design choice from #198.

CLAUDE.md gets a "Read first" section pointing at both so future Claude
sessions discover them at session start.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(glossary): correct product-terminology entries against learn.jamf.com

Validated the glossary against Jamf Product Documentation via Ask Jamf MCP.
Several entries had errors from working off codebase signals + training-era
knowledge rather than authoritative docs:

- **Scope (biggest correction):** Scope in Jamf Pro is the *unified*
  concept comprising three sub-functions: targets, limitations, exclusions.
  Targets are *part of* scope, not a separate Platform-only concept.
  Limitations and exclusions weren't in the glossary at all — added,
  along with the rule "exclusions always override targets and limitations."

- **Blueprint:** Originally described as "the Platform API's DDM container."
  Actually a Jamf Pro AND Jamf School feature, built on DDM, scoped to
  smart/static groups. Available in both products. Now also supports
  configuration-profile payloads alongside declarations.

- **DDM:** Was framed as "Apple's newer pull-based config model." Per
  Apple/Jamf docs, DDM is an *additive* layer on top of MDM, not a
  replacement. Devices apply declarations proactively and report state
  via the *status channel*. Added the status channel as its own entry.

- **PreStage enrollment:** Capitalization fix (capital S). Added the
  "settings sync with Apple every two minutes" detail.

- **ADE / DEP:** Per Jamf School docs, "DEP" is *explicitly* the formerly-
  used name. Tightened wording. Added Account-driven Device Enrollment as
  the macOS 14+ alternative to PreStage.

- **Compliance Benchmarks:** Built on the macOS Security Compliance Project
  (mSCP) framework — added context. The UI uses "benchmark templates" +
  "rules" + "ODVs"; "baseline" is the Platform-API/CLI term and creates a
  naming gap. Now documented as such instead of presented as a single layer.

- **Extension Attributes:** Added the auto-generated-per-benchmark pattern
  (`[Benchmark Name] - Failed Result List`).

Added a Sources section pointing at the learn.jamf.com docs consulted and
calling out that this is a point-in-time capture that should be updated as
product renames happen (DEP → ADE, Azure ID → Microsoft Entra ID, etc.).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil Martin <neil.martin@jamf.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants