Read ARCHITECTURE.md first for the package map. This file is the
short workflow checklist for landing the three most common kinds of
change.
git clone https://github.com/c3xdev/c3x.git
cd c3x
go test -race ./... # full suite
go run ./cmd/verify_catalog # catalog health against pricing.c3x.devThe repo enforces gofmt, go vet, and depguard (module
boundaries). CI runs all three; locally:
gofmt -s -w .
go vet ./...-
Create
resources/<provider>/<kind>.toml. Skeleton:kind = "aws_widget" display_name = "AWS Widget" provider = "aws" [mappings.widget] service = "AmazonWidget" product_family = "Widget" attribute_filters = [ { key = "instanceType", expr = 'default(size, "small")' }, ] [[dimensions]] id = "widget_hours" label = "Widget" unit = "hours" quantity = "monthly_hours()" rate = 'price("widget")' [fixture] attributes = { size = "small" } expected_monthly_cost = 0 tolerance = 0.05
-
Probe the upstream backend to find the right mapping shape:
curl -s 'https://pricing.c3x.dev/graphql' \ -H 'content-type: application/json' \ -d '{"query":"{ products(filter: { vendorName: \"aws\", service: \"AmazonWidget\", region: \"us-east-1\" }) { productFamily attributes { key value } prices { USD } } }"}'
-
Run the verifier to compute the actual monthly cost:
go run ./cmd/verify_catalog | grep aws_widget -
Update the fixture's
expected_monthly_costto that value. The verifier now enforces it on every run as a snapshot. -
Add the kind to the appropriate parser's attribute extractor if the Terraform attribute names need normalization (see
internal/parser/terraform/resources.gofor examples).
If the upstream returns no products, fall back to inline rates:
rate = "0.05" # static — document the gap in docs/upstream-gaps.mdexact = true in the fixture instead of tolerance so any drift
fails CI loudly.
-
Pick
rules_aws.go,rules_azure.go, orrules_gcp.gobased on the cloud. (Cross-provider rules go inrecommend.go.) -
Implement
Rule:type WidgetUnderutilization struct{} func (WidgetUnderutilization) Name() string { return "aws.widget.under" } func (WidgetUnderutilization) Propose(r domain.Resource) []Proposal { if r.Ref.Kind != "aws_widget" { return nil } // … return one or more Proposals }
-
Register it in the per-cloud factory (
AWSRules(), etc.). -
Add tests in
recommend_test.goand the table-drivenrules_test.go.
For cross-resource recommendations use TreeRule.ProposeTree and
return a TreeProposal with the Changes map. The engine
re-estimates the modified set; positive net delta becomes a
recommendation.
internal/render/<format>.gowithRender<Format>(est) string(or(string, error)for serialization formats).- Add the constant to
internal/render/render.go'sFormatenumString()+ParseFormat().
- Add a
casetoRender()andRenderDiff()ininternal/render/dispatch.go. - Test the rendering against a hand-crafted
domain.Estimate— no full pipeline needed.
- Implement
comment.Posterininternal/comment/<forge>.go. Reuse the marker pattern (comment.Marker). - Add an env-driven
AutoDetect<Forge>()helper. - Add a subcommand in
cmd/c3x/comment.gothat wires flags + constructor + dispatch. - Test with
httptest.NewServerstubbing the forge's API.
-
go test -race ./...must pass before submitting. -
Property tests live next to the unit tests (
recommend_test.go,concurrency_property_test.go). -
Fuzz tests go in
<package>/fuzz_test.gowith seed corpora. -
Golden tests update with
-update:go test ./cmd/c3x/... -run TestEstimateGolden -update -
Catalog snapshots regenerate by editing the affected TOML's
[fixture]block + running the verifier.
The verifier reports [DRIFT] when a kind's computed cost moves
outside its snapshot tolerance. Drift is not always a bug — vendor
prices change. When you see drift:
- Confirm against the vendor's public pricing page that the new rate is correct.
- Update the affected TOML's
expected_monthly_cost. - Bump
last_verified(when we add that field — A.3.1 on the roadmap). - Commit with a message like
catalog: refresh aws_eks rate.
Single-purpose commits. Conventional Commits style preferred:
catalog: add aws_workspaces_workspace TOML
parser: handle CFN !Sub with mid-string variables
calculator: fix cache key collision on multi-mapping filters
PR descriptions should link to the relevant ROADMAP.md item if
one exists.