The open-source CLI: @optiqor/cli. Apache-2.0. Standalone repo so it stays independently auditable, which is the entire reason it doesn't live in the proprietary monorepo. Strategy reference: docs/open_source_cli_playbook.md in the Optiqor org docs (not public).
This file is the operating manual. Read it before writing or reviewing code.
- Go 1.24, single module:
github.com/optiqor/optiqor-cli - Cobra for command parsing, charmbracelet/lipgloss for terminal rendering
- Pure stdlib for the rest. No HTTP framework, no logger library, no DI framework.
- npm wrapper (
@optiqor/cli) downloads the platform-specific Go binary at install time - GoReleaser for cross-platform builds (linux + darwin, amd64 + arm64)
- Cosign for release artifact signing
- SHA-256 verified on download (in progress, see open issues)
If you reach for a new dependency, justify it in the PR. Adding a transitive dep widens the audit surface of an Apache-2.0 binary that runs on customer laptops.
These exist because the OSS funnel breaks if any of them slip. They are not preferences.
-
No LLM calls. The CLI is a deterministic rule engine. The Sonnet/Opus/Haiku-driven Apply Fix flow lives in the proprietary backend. If you find yourself wanting to call an LLM from here, the answer is "POST to the SaaS sandbox endpoint instead and let it return the LLM-enriched response."
-
No telemetry by default. Zero-config install must not phone home. The only opt-in network egress is
--share, which POSTs a sanitised analysis tooptiqor.dev/r/<hash>. Every new network call needs an explicit user opt-in and must consultofflineMode()incmd/optiqor/main.go. -
Accuracy disclosure in every output. Every renderer (text, JSON, HTML, roast) emits the mandatory disclosure:
Sandbox accuracy: ±40%. Install the Optiqor agent for exact numbers (optiqor.dev/get).The string is canonical. The honesty is the whole pitch; don't water it down. -
No proprietary backend code may be imported.
go.modmust never referencegithub.com/optiqor/optiqor. The CLI is independently buildable, auditable, licensable. CI greps imports on every PR. -
pkg/is the stable public API. External programs may import it, including the proprietary backend (which depends onpkg/rulesandpkg/parser). Breaking changes go through semver and a deprecation notice. New detectors land inpkg/rulesfirst; the backend follows automatically viago get -u. -
internal/is private. Refactor freely.internal/{analyze,render,share,config,roast}is CLI-side composition that stays out of the public API.
- Primary:
npm install -g @optiqor/cliornpx @optiqor/cli analyze ./chart. The npmpostinstallscript downloads the right binary from GitHub Releases. - Secondary:
go install github.com/optiqor/optiqor-cli/cmd/optiqor@latest. - Releases are GoReleaser-built (
-trimpath, version ldflag fromgit describe) and Cosign-signed. - We do not publish to Homebrew or Cargo in Year 1. npm is where the platform engineers are.
- Windows is out of scope.
.goreleaser.yamlandpackage.jsonboth reflect this. Adding Windows handling needs the OSS-scope exclusion in the playbook removed first.
12 commands phased over 12 months. Year 1 ships the deterministic core.
| Command | Status | Purpose |
|---|---|---|
analyze <chart> |
shipped | Primary entry point. Reads a Helm chart dir or values.yaml, prints findings. |
demo |
shipped | Runs analysis on a bundled chart so npx @optiqor/cli demo shows output without input. |
diff <a> <b> |
shipped | Cost delta between two values files. |
score <chart> |
shipped | 0-100 efficiency score (qualitative band Year 1, numeric Year 2). |
audit <chart> |
shipped | Security-focused subset of analyze. Stricter --fail-on defaults. |
watch <chart> |
stub | Re-runs analyze on file change. Phase 2+. |
compare <a> <b> |
stub | Side-by-side renderer. Phase 2+. |
--roast (flag) |
shipped | Humorous tone on analyze. Findings stay accurate. |
Stubs return "not yet implemented" and exit 1. The surface is registered so the command name is locked in.
The CLI's output is the product. Treat it with the same care as code.
stdoutis data: the report, JSON, or HTML when piped.stderris everything else: status, warnings, errors, share URLs, the accuracy disclosure when output is redirected.- Mixing the two breaks pipelines.
optiqor analyze ./chart | jqmust always work without2>/dev/nulltricks.
- Respect
NO_COLOR(de-facto standard, https://no-color.org). - Respect
--no-color. - Auto-detect via
isatty(stderr). Don't auto-color when piped.
0success, no findings at or above--fail-on1runtime error (parse failure, file not found, invalid flag)2findings at or above--fail-onseverity
Reserved: 3-127 for future use. Don't repurpose Unix-standard codes (130 SIGINT, 143 SIGTERM).
Errors reach the user. Each one must include:
- What happened, in user terms.
- What to do next, specific.
Bad:
Error: invalid input
Good:
error: --severity must be one of low, med, high (got: "medium")
For multi-line context, use a newline and indent the actionable next step.
Every command and flag has help text. It's product surface: what users read before they try anything.
- One-line summary on the command line, one paragraph in the long description.
- The long description names the input shape, the output shape, and at least one runnable example.
- Examples use
./my-chart, notpath/to/chart.
Bad flag description:
"output format"
Good:
"emit machine-readable JSON instead of the human table"
The CLI is deterministic. Same input, same output, byte-identical.
- No timestamps in output unless explicitly requested.
- No random map iteration. Sort before serialising.
- No reads of
$HOMEor$USERin the analyze path. Config file path is a separate concern. - Time and randomness injected. Never
time.Now()directly in any package with a state machine. - Goroutine concurrency only for parallel rule execution, with results merged in deterministic order.
Golden tests in testdata/golden/ assert byte-identical output across runs. A flake here is a determinism bug, not a test bug. Regenerate with -update; eyeball the diff before committing.
- Table-driven by default. One
Test<Func>per SUT method, one row per scenario,t.Run(tc.name, ...)so failures name the row. Do not suffix names with_TableDriven— every test should already be table-driven, so the marker is noise. - Unit tests beside the code:
foo.go+foo_test.go. - Stdlib only — no testify, no gomock.
errors.Is/errors.Asfor error checks,bytes.Equalfor bytes, golden files for multi-line / whitespace-sensitive output. - Race detector always:
go test -race -count=1 ./...is the CI bar. - Inject time + randomness —
time.Date(...),bytes.NewReader, nevertime.Now()/rand.Intnin code under test. - Helpers call
t.Helper()on the first line. - Golden tests in
testdata/golden/for every renderer mode. The mandatory ±40% accuracy disclosure is asserted byte-identical across modes. Regenerate withUPDATE_GOLDEN=1 go test ./...and eyeball the diff before committing. - Every detector in
pkg/rulesneeds at least one positive and one negative table row. go test ./...is the only test command. No separate integration suite — the CLI is pure functions over Helm input.
pkg/ (the public surface) has stricter test discipline. Coverage target: 90%. External tests (pkg/foo_test.go in package foo_test) for any exported symbol with a non-trivial contract.
See .claude/skills/test/SKILL.md for the full procedure: templates, anti-patterns, coverage targets, and references to clean examples in the repo.
Comments explain WHY, never what. Decoration is debt; every comment is a thing a future engineer must keep in sync with the code. When in doubt, delete. Re-add only when a reader would miss a non-obvious constraint, trade-off, ADR reference, security/performance rationale, or invariant.
The CLI is Apache-2.0 OSS, so pkg/ is the public surface that external Go programs import. Lean slightly more conservative there: one-line godoc on every exported symbol is appropriate when it documents a contract beyond the name (// Run executes all detectors against the workloads and returns findings in detector-declaration order is a real contract; // Run runs the detectors. is not).
Banned:
- Markdown headers (
#,##,###) inside//comments. Note:,Important:,Caution:,Warning:labels. If it's important, the code structure should make the rule unmissable.- Em-dash-heavy narrative essays in package or function docstrings. One terse sentence beats five lines of glue.
- Decorative section dividers:
// ─── Helpers ───,// ====== validators ======. Use blank lines. - Multi-paragraph package docstrings narrating "Layout philosophy:", "Implementation notes:", "Design constraints:". Compress to one or two terse sentences.
- Bullet-listed enumerations of struct fields or function returns — the type tags already enumerate them.
- Restating the signature (
// Foo returns a Foo.) or the next line of code. - "Helper function to...", "Utility for...", "This function..." preambles.
- Emojis. None, anywhere.
- TODOs without a name and issue:
// TODO(@shivam, #42): ...is fine;// TODO: do lateris not.
Always keep:
- The CLI's hard-rule pins: no LLM in CLI, no telemetry by default, ±40% accuracy disclosure is mandatory in every renderer output. Comments that pin these stay verbatim.
- Detector references to CIS / NSA hardening guides — those anchors are load-bearing for security findings.
- Cross-file references to other packages, ADRs, or upstream specs.
How to audit a comment: ask "if I delete this, what does the next reader fail to understand?" If the answer is "nothing — the name + signature + types already say it", delete. If the answer names a specific constraint, trade-off, or hard-rule pin, keep but compress.
Reference commits: the 2026-05-24 cleanup (feba8e0 openapi header, 9f817cd cli sweep) stripped ~400 lines of comment cruft from this repo. Read those diffs for the tone applied at scale; new code should land at that compression level from the start.
- Functions are verbs (
ParseValues,RenderReport). - Predicates:
isLeader,hasQuorum, notleaderFlag. - Acronyms keep case:
httpClient,parseURL,IDToken. NotHttpClient. - Cobra command vars:
analyzeCmd. Constructors:newAnalyzeCmd().
- One thing per function. If the name has "and", split.
- Early return for guard clauses. No
elseafterreturn. - Receiver names: one or two letters, consistent across all methods of a type.
- Wrap with
%w, never%s, when the caller mighterrors.Is/errors.As. - Sentinel errors exported only when callers branch on them.
- Never
panicfor control flow. Reserved for "this cannot happen". - Distinguish bad input (user error, exit 1, friendly message) from bug (program error, exit 1, full context).
Comments explain why, never what. Code says what; you say why it has to.
Bad (states the obvious, AI-shaped):
// Iterates through detectors and runs each one.
for _, d := range detectors {
d.Run(w)
}Good (explains the why):
// Sequential on purpose. Detectors share a finding map that's faster
// to merge than to lock per-write. ADR-0003.
for _, d := range detectors {
d.Run(w)
}Bad (AI godoc):
// RunDetectors runs all detectors on the workloads.
// Returns a slice of findings.
func RunDetectors(ws []Workload, ds []Detector) []FindingGood (godoc that earns its keep):
// RunDetectors evaluates ds against every workload in ws and returns
// findings sorted by (severity desc, detector id asc) for stable
// golden tests. Detectors are evaluated in registration order; the
// public rules.All() returns them in canonical order for reproducible
// output.
func RunDetectors(ws []Workload, ds []Detector) []FindingReference real things: issue numbers, commit SHAs, ADR numbers, RFC sections, vendor docs.
// Helm v3 sets release.Namespace at render time, not template time.
// We default to "default" so values.yaml that uses .Release.Namespace
// in a template doesn't crash. See helm/helm#7702.Banned in comments:
- Markdown headers (
#,##,###). Note:,Important:,Caution:,Warning:labels.- Emojis. None, anywhere.
- Restating what the code says.
- "Helper function to...", "Utility for...". The name should describe it.
- Author tags. Git blame exists.
- TODOs without a name or issue link.
Godoc on every exported symbol. The first sentence starts with the symbol name and is one line. Examples for non-trivial APIs go in example_test.go.
- Lowercase file names, no underscores except
_test.go. doc.goper package, package comment lives there.- Package names: short, single-word, no plurals, no
util, nohelpers.
The CLI ships in front of users. The text is the product.
brand/tokens.jsonis the single source of truth for colors, font tokens, glyph references. Imported bypkg/htmlrender(Go) and by the backend'sweb/src/lib/brand.ts(TS). Drift here breaks visual parity.- Tone is editorial × engineering. Direct, sharp, opinionated, never glib. No exclamation marks in default output.
--roastexists for the snark. - Numbers are accurate or absent. We never round up.
±40%is canonical. - No emojis in default output.
--roastmay use a small whitelist (ininternal/roast).
CLI binaries run on customer laptops. The supply chain is a primary attack surface.
- Releases are GoReleaser-built with
-trimpathand-buildvcs=true. Build determinism is asserted by the release job. - Cosign signs the archive and
checksums.txt. The npm postinstall verifies the SHA-256 of the downloaded archive againstchecksums.txtbefore extracting. - The CLI never opens files outside the supplied chart path without an explicit flag. Path traversal in
--config,--html,--outputis a hard rejection, not a warning. - YAML deserialization uses
gopkg.in/yaml.v3with anchor depth bounded. Billion-laughs is rejected at parse time. - npm
postinstallwrites only to the package's ownvendor/directory. Any path that resolves abovenode_modules/@optiqor/cli/is a P0. - No
exec.Commandin the binary. The CLI is a pure-Go process. No shelling out, nokubectlspawning, no helm-CLI dependency.
<type>/<short-kebab-slug>. Same prefixes as commits:feat/,fix/,chore/,docs/,test/,refactor/.- Branch off
main. Never branch off a feature branch. - Delete merged branches.
Conventional Commits. One concern per commit. DCO sign-off required (-s flag, enforced by CI).
Local quality gate before pushing: gofmt -l . (must be empty), go vet ./..., go build ./..., go test -race -count=1 ./..., golangci-lint run --timeout=2m ./....
- Open against
main. Branch protection ruleset is active. Admin bypass only for emergencies. - Title is a Conventional Commits subject; it becomes the squash-merge subject.
- Body tells the reviewer the story: what changed, why, how to verify.
- Link the issue:
Closes #N. - DCO sign-off enforced by CI (
.github/workflows/commit-lint.yml). - PR size labels auto-apply (
size/XSthroughsize/XL). XL is a smell. Split. - markdown-link-check runs in CI. Cross-repo relative links (
../) won't resolve in standalone checkout; use absolute URLs or remove the link.
One inline comment per issue, anchored to the exact line. Short summary at the end. Terse maintainer voice — no em-dashes, no markdown headers in the comment body, no bold-labels.
One issue per finding. Repo-correct (optiqor-cli/ for OSS-surface bugs, optiqor/ for backend). Title is a single declarative sentence. Body is plain prose, no ## Problem / ## Expected Behavior headers.
ADRs in docs/adr/ for non-trivial architectural changes. Template at docs/adr/0000-template.md. Examples:
- A new exported package under
pkg/ - A change to the deterministic-output contract
- A new dependency that adds network or filesystem reach
- A change to brand tokens that affects existing renderers
- A change to the public
rules.Findingorrules.Workloadshapes (downstream backend depends on these)
- Don't import any GitHub / GitLab API client. The CLI doesn't talk to source control.
- Don't add a daemon, watcher, or persistent process. CLI invocations are short-lived.
- Don't add config files unless absolutely necessary. Flags + env vars only.
OPTIQOR_*env vars are documented in README. - Don't add platform-specific code paths. The CLI runs identically on linux and macOS.
- Don't use
init()except to register Cobra commands. - Don't write a singleton. Pass dependencies through constructors.
- Don't depend on
init()ordering. - Don't return
interface{}/anyunless you've considered generics. - Don't shell out (
exec.Command). Pure-Go binary. - Don't read
$HOMEor$USERin the analyze path. Config-file lookup is separate and documented.
- Don't print to stdout from anywhere except the renderer.
- Don't auto-color when piped.
- Don't restate the input in the output ("Analyzing ./chart...") unless
--verbose. - Don't print stack traces to users. They go to stderr only if
DEBUG=1.
- Don't import any code from
github.com/optiqor/optiqor. The CLI's audit story is "Apache-2.0 and you can read every line." - Don't commit binary artifacts. Generated files go in
.gitignore. - Don't TODO without an issue. Open the issue, paste the link, then write the TODO.
- Don't merge to
mainwithout a PR. - Don't bump dependencies without a one-line justification in the commit message.
- Don't break
pkg/without a semver bump and a deprecation notice. The backend imports it directly viago get -u.