feat(lint): add dead-code lint for flags and unexported helpers#198
Merged
Conversation
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>
5 tasks
neilmartin83
approved these changes
May 11, 2026
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
scripts/lint-dead-code/that AST-walksinternal/commands/and surfaces dead Cobra flag bindings + dead unexported helpers.Annotations["lint:keep-flag"]for flags and//lint:keepdoc comments for funcs.make lint-dead) withcontinue-on-error: true— warn-only for the first 2 weeks.--gateonce 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 toounreliable 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-flagannotations,//lint:keepdoc comments) generalisesto future structural verifiers (anti-reimplementation, MCP-surface-parity,
naming-consistency).
Behavior contract
Dead flags: call sites matching
<cmd>.{Persistent}Flags().XVar(&backing, "name", ...)where thebackingident has zero reads anywhere in the same package besides the binding&IDENTand the declaration site. Scans test files too — test reads count as legitimate use.Dead funcs: top-level unexported funcs in
internal/commands/(recursive, includingpro/generated/andplatform/generated/) with zero same-package references besides the declaration. Skipsinit,main,Test*,Benchmark*,Fuzz*,Example*, and methods.Allowlist (flags): add
Annotations: map[string]string{"lint:keep-flag": "name1,name2"}to the owning*cobra.Commandcomposite literal. Statically resolved on a best-effort basis — if the literal isn't statically resolvable, no allowlist applies.Allowlist (funcs): add
//lint:keepto the func's doc-comment group. Bare marker accepted for now; rationale convention may be enforced later.First-sweep findings
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 ./...passesgo test ./...passes (new scanner tests + full suite)make lintclean (golangci-lint)make verify-generatedpassesmake lint-deadruns cleanly (no findings)clean,dead-flag,dead-func,allowlisted) cover the positive + suppression pathsWarn-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 thecontinue-on-error.🤖 Generated with Claude Code