Vibecode Infracost Cloud
track this build4 phases, 12 steps, beginner friendly0%The core developer loop is a credible weekend build if the replacement is intentionally narrow: one GitHub App, Terraform on AWS, a small resource catalog, public list prices, basic tag and cost guardrails, and one idempotent pull-request comment. A faithful Infracost Cloud replacement is much harder because the paid product combines broad IaC and multi-cloud coverage with continuously maintained pricing data, policy depth, custom price books, organization-wide visibility, workflows, and enterprise integrations.
You are building a lean indie version of Infracost Cloud. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Infracost Cloud · indie build A cost estimation tool for infrastructure changes whose value depends entirely on being honest about coverage: the fixed costs are computable from the definitions, the usage-driven ones are not, and conflating them produces confident wrong numbers people act on. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Parsing | Infrastructure definitions parsed to a resource graph, provider-specific | the estimate is only as good as the resource shapes you resolve | | Pricing | A dated price catalogue per provider and region | prices change and a stale catalogue is a wrong estimate that looks fine | | Coverage | Explicit per-resource support state, with unsupported ones counted | silently skipping a resource understates the total | | Usage | Declared assumptions for usage-driven costs, never inferred | guessing egress volume produces a number nobody should trust | | Reporting | Diffs against a baseline, in a pull request comment | the useful moment is before the change is merged | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the parser, the pricing layer and the reporting. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **The infrastructure tool whose definitions you will parse** · free - Why: Parsing the source text is far less reliable than parsing a resolved plan, and the plan format is what resolves variables and counts. - Get it: The tool itself, plus a repository whose plan you can generate. - Verify: A plan output is generated and parsed into concrete resources - [ ] **Access to a provider price catalogue** · free to read - Why: Prices vary by region and change, and hard-coding a sample produces estimates that drift without breaking. - Get it: The provider's own pricing interface, and record the date you fetched. - Verify: Prices for one resource kind differ correctly between two regions - [ ] **A real infrastructure repository** · free - Why: Modules, variables and counts are where resolution actually fails, and a single-file example exercises none of it. - Get it: A repository with modules, variables and at least one resource whose cost is dominated by usage. - Verify: The repository parses with at least one unresolved and one usage-driven resource - [ ] **A real bill for that infrastructure** · free if you own the infrastructure - Why: The honesty of the coverage reporting is only assessable by comparing an estimate against what was actually charged. - Get it: A recent bill for the environment your repository describes, broken down by service. - Verify: The estimate and the bill are compared and the gap is explained by the stated coverage ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Billing reconciliation against actual usage. - Provider and resource breadth. - Commitment and discount modelling. - Any claim that an estimate predicts a bill. - AWS, Azure, and Google Cloud coverage across the full Infracost resource catalog - Terragrunt, CloudFormation, AWS CDK, modules, and complex Terraform expression coverage - custom price books, negotiated discounts, and organization-specific pricing - Issue Explorer, campaigns, AutoFix, dashboards, audit trails, and team workflows - enterprise identity, security, support, and broad CI/CD integrations If one of those is essential to you, that is the reason to keep paying for Infracost Cloud, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Infracost Cloud The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me cost estimates on infrastructure changes, honest about what they cannot include to replace Infracost Cloud. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero An infrastructure cost estimate is a lower bound on a subset, and presenting it as a monthly figure invites a decision it cannot support. What a definition file tells you is what resources exist and how they are shaped. What it cannot tell you is how much data moves between them, how many requests arrive, how much is stored after a year, or how often something scales. For a great many architectures those usage-driven costs exceed the fixed ones, and data transfer in particular is both large and entirely invisible in the definitions. So every estimate here separates what is known from what is not, states which resources have usage-driven costs that are excluded, and never presents a total without that alongside. An estimate that says forty pounds a month when the real figure is four hundred because of egress is worse than no estimate, because somebody approved it. ### Stack (fixed, do not substitute) - Parsing: Infrastructure definitions parsed to a resource graph, provider-specific · the estimate is only as good as the resource shapes you resolve - Pricing: A dated price catalogue per provider and region · prices change and a stale catalogue is a wrong estimate that looks fine - Coverage: Explicit per-resource support state, with unsupported ones counted · silently skipping a resource understates the total - Usage: Declared assumptions for usage-driven costs, never inferred · guessing egress volume produces a number nobody should trust - Reporting: Diffs against a baseline, in a pull request comment · the useful moment is before the change is merged ### Data model (create this before Phase 1) - runs(id, repository, commit_sha, base_sha, ran_at, definitions_hash) - resources(run_id, address, kind, region, attributes_json, support_state) · support_state is priced, usage_driven or unsupported - prices(provider, region, sku, unit, amount_minor, currency, effective_from, fetched_at) - estimates(run_id, resource_address, fixed_minor, usage_minor, usage_assumptions_json) - usage_profiles(id, name, assumptions_json, source) · source is declared or default, never inferred - diffs(run_id, base_run_id, resource_address, change_kind, delta_minor, coverage_changed) - An estimate stores fixed and usage-driven components separately, so a total is never reported without saying how much of the cost is not in it. ### Phase 1 · Resolving what the definitions actually create Build: Parse infrastructure definitions or a plan output into a resource graph with sizes, regions and counts resolved. Handle variables, modules and counts. Report a resource whose shape cannot be resolved rather than assuming a default. Trap: Assuming a default size for a resource whose shape depends on an unresolved variable produces an estimate for infrastructure nobody is deploying, and it looks identical to a correct one. Report unresolved shapes as unresolved and exclude them from the total rather than guessing. Done when: A real repository's definitions resolve to concrete resources with sizes and regions. Modules and counts are expanded correctly. An unresolved variable produces an unresolved resource rather than a defaulted one. The count of unresolved resources is reported alongside any total. Do not build yet: No pricing, coverage or diffs. ### Phase 2 · Prices with a date on them Build: Fetch and cache provider prices per region with the date fetched. Report the catalogue age with every estimate. Refuse to use a catalogue older than a stated threshold without an acknowledgement. Trap: A price catalogue cached indefinitely produces estimates that drift from reality without anything appearing to break, and the first sign is a bill that does not match a report from six months ago. Date the catalogue, show its age, and refuse to run silently on a stale one. Done when: Every price carries the date it was fetched and the catalogue age is shown with every estimate. A catalogue older than the threshold refuses to produce an estimate without an explicit acknowledgement. Region-specific pricing is used rather than a single default region. A price missing for a resource is reported rather than treated as zero. Do not build yet: No coverage, usage or diffs yet. ### Phase 3 · Saying what is not in the number Build: Classify each resource as priced, usage-driven or unsupported. Report the count and significance of each class alongside the total. Never omit an unsupported resource silently. Trap: Skipping resources the tool does not support, and reporting the total of the rest, produces a number that is confidently too low, and the resources most likely to be unsupported are often the expensive new ones. Count them, name them, and put the count where the total is. Done when: Every resource is classified as priced, usage-driven or unsupported and the counts appear beside the total. A resource with usage-driven costs is named as such rather than being priced at its fixed component alone. An unsupported resource is listed rather than omitted. Coverage is expressed as a proportion of resources, not of cost, since the cost of the excluded part is unknown. Do not build yet: No usage assumptions or diffs yet. ### Phase 4 · Usage assumptions, declared rather than guessed Build: Accept declared usage profiles for the resources that need them, from a file in the repository. Report the assumptions used beside the figures they produced. Never infer usage from resource shape. Trap: Inferring egress volume from an instance size produces a number with no basis that will nonetheless be quoted in a decision, and data transfer is precisely where estimates go most badly wrong. Require a declared profile, and where none exists report the resource as usage-driven with no figure rather than estimating one. Done when: Usage-driven costs appear only for resources with a declared profile. The assumptions used are printed beside every figure they produced. A resource with no declared usage is reported as usage-driven without a number rather than defaulted. Changing a usage profile changes the estimate and the change is attributable to the profile. Do not build yet: No diffs or integration yet. ### Phase 5 · The diff, where the decision happens Build: Compare a branch against its base and report the delta per resource in a pull request comment. Flag a change in coverage as well as in cost. Handle a first run with no baseline and a change that only moves resources between modules. Trap: Reporting a cost delta without reporting a coverage change lets a refactor that swaps a supported resource for an unsupported one appear as a cost reduction, when in fact the cost simply stopped being counted. Report coverage changes as prominently as cost changes. Done when: A branch reports its cost delta per resource against the base. A change that reduces coverage is flagged as prominently as a cost change, tested by replacing a supported resource with an unsupported one. A first run with no baseline reports the total rather than a delta. Moving a resource between modules does not appear as a deletion and an addition. Do not build yet: Nothing after this. ### Out of scope (and why) - Actual billing reconciliation, which requires the provider's own usage data and is a different product. - Provider and resource breadth, which is the hosted product's real value and an endless maintenance commitment. - Commitment and discount modelling, which depends on contracts the definitions know nothing about. - Any claim that an estimate predicts a bill, which it does not and cannot for usage-driven architectures. ### README must contain - That an estimate is a lower bound on a subset, since definitions cannot express usage - That every resource is classified as priced, usage-driven or unsupported and the counts appear beside the total - That usage costs require a declared profile and are never inferred from resource shape - That the price catalogue is dated and a stale one refuses to run silently - That a coverage reduction is flagged as prominently as a cost change ===== AGENTS.md ===== # Agent instructions · Infracost Cloud indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Infrastructure definitions parsed to a resource graph, provider-specific, A dated price catalogue per provider and region, Explicit per-resource support state, with unsupported ones counted, Declared assumptions for usage-driven costs, never inferred, Diffs against a baseline, in a pull request comment. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Assuming a default size for a resource whose shape depends on an unresolved variable produces an estimate for infrastructure nobody is deploying, and it looks identical to a correct one. Report unresolved shapes as unresolved and exclude them from the total rather than guessing. - A price catalogue cached indefinitely produces estimates that drift from reality without anything appearing to break, and the first sign is a bill that does not match a report from six months ago. Date the catalogue, show its age, and refuse to run silently on a stale one. - Skipping resources the tool does not support, and reporting the total of the rest, produces a number that is confidently too low, and the resources most likely to be unsupported are often the expensive new ones. Count them, name them, and put the count where the total is. - Inferring egress volume from an instance size produces a number with no basis that will nonetheless be quoted in a decision, and data transfer is precisely where estimates go most badly wrong. Require a declared profile, and where none exists report the resource as usage-driven with no figure rather than estimating one. ===== BUILD_PLAN.md ===== # Build plan · Infracost Cloud A cost estimation tool for infrastructure changes whose value depends entirely on being honest about coverage: the fixed costs are computable from the definitions, the usage-driven ones are not, and conflating them produces confident wrong numbers people act on. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Resolving what the definitions actually create A plan resolves to concrete resources with their shapes. ### Steps 1. Resolve shapes, do not default them A defaulted size is an estimate for infrastructure nobody is deploying. Files: `src/parse.ts` 2. Expand modules and counts Where the resource count actually comes from. Files: `src/expand.ts` 3. Report unresolved resources Alongside the total, not in a log. Files: `src/report.ts` ### Done when - [ ] Definitions resolve to concrete resources - [ ] Modules and counts expand correctly - [ ] Unresolved variables produce unresolved resources - [ ] Unresolved counts appear with the total ### Watch out - Assuming a default size for a resource whose shape depends on an unresolved variable produces an estimate for infrastructure nobody is deploying, and it looks identical to a correct one. Report unresolved shapes as unresolved and exclude them from the total rather than guessing. ## Phase 2 · Prices with a date on them Every price used is dated and its staleness is visible. ### Steps 1. Date the catalogue and show its age A stale catalogue is a wrong estimate that looks entirely normal. Files: `src/prices.ts` 2. Use region-specific prices The same resource differs substantially between regions. Files: `src/prices.ts` 3. Report missing prices as missing Treating an absent price as zero understates the total silently. Files: `src/estimate.ts` ### Done when - [ ] Prices carry fetch dates and the age is shown - [ ] A stale catalogue refuses without acknowledgement - [ ] Region-specific pricing is used - [ ] Missing prices are reported rather than zeroed ### Watch out - A price catalogue cached indefinitely produces estimates that drift from reality without anything appearing to break, and the first sign is a bill that does not match a report from six months ago. Date the catalogue, show its age, and refuse to run silently on a stale one. ## Phase 3 · Saying what is not in the number Every estimate states its coverage explicitly. ### Steps 1. Classify every resource Priced, usage-driven, unsupported. Three states, all reported. Files: `src/coverage.ts` 2. Put coverage beside the total Not in a footnote. It is what makes the total interpretable. Files: `src/report.ts` 3. Express coverage by resource count By cost is impossible, since the excluded cost is what you do not know. Files: `src/coverage.ts` ### Done when - [ ] Every resource is classified into three states - [ ] Coverage appears beside the total - [ ] Usage-driven resources are named - [ ] Coverage is expressed by resource count ### Watch out - Skipping resources the tool does not support, and reporting the total of the rest, produces a number that is confidently too low, and the resources most likely to be unsupported are often the expensive new ones. Count them, name them, and put the count where the total is. ## Phase 4 · Usage assumptions, declared rather than guessed Usage-driven costs appear only where someone stated the usage. ### Steps 1. Take usage from a declared file In the repository, reviewable alongside the change. Files: `src/usage.ts` 2. Print assumptions with the figures So a wrong number is traceable to a wrong assumption. Files: `src/report.ts` 3. Never infer usage from shape Egress from instance size is a number with no basis. Files: `tests/no-inference.test.ts` ### Done when - [ ] Usage costs require a declared profile - [ ] Assumptions print beside their figures - [ ] Undeclared usage produces no number - [ ] Profile changes are attributable ### Watch out - Inferring egress volume from an instance size produces a number with no basis that will nonetheless be quoted in a decision, and data transfer is precisely where estimates go most badly wrong. Require a declared profile, and where none exists report the resource as usage-driven with no figure rather than estimating one. ## Not in this build - Billing reconciliation against actual usage. - Provider and resource breadth. - Commitment and discount modelling. - Any claim that an estimate predicts a bill. ## After v1, if you want it - Comparing estimates against the eventual bill, which is the only way to calibrate coverage honestly - Policy checks such as refusing a resource kind, which the resource graph already supports - Usage profiles derived from the previous month's actual usage, declared rather than inferred ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Dated per fetch. A stale catalogue refuses to run silently. PRICE_CACHE_PATH=./cache/prices.json # Required. Beyond this an estimate requires an explicit acknowledgement. PRICE_MAX_AGE_DAYS=14 # Required. Declared usage. Absent means usage-driven resources report no figure. USAGE_PROFILE_PATH=./infracost-usage.yml # Required. Present as a constant. Inferred egress is a number with no basis. INFER_USAGE=never # Required. Present as a constant. A total without coverage is not interpretable. REPORT_COVERAGE=always # Required. Used only where a resource does not state one, and reported when it is. DEFAULT_REGION=eu-west-1
You are building a lean indie version of Infracost Cloud. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Infracost Cloud · indie build A cost estimation tool for infrastructure changes whose value depends entirely on being honest about coverage: the fixed costs are computable from the definitions, the usage-driven ones are not, and conflating them produces confident wrong numbers people act on. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Parsing | Infrastructure definitions parsed to a resource graph, provider-specific | the estimate is only as good as the resource shapes you resolve | | Pricing | A dated price catalogue per provider and region | prices change and a stale catalogue is a wrong estimate that looks fine | | Coverage | Explicit per-resource support state, with unsupported ones counted | silently skipping a resource understates the total | | Usage | Declared assumptions for usage-driven costs, never inferred | guessing egress volume produces a number nobody should trust | | Reporting | Diffs against a baseline, in a pull request comment | the useful moment is before the change is merged | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the parser, the pricing layer and the reporting. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **The infrastructure tool whose definitions you will parse** · free - Why: Parsing the source text is far less reliable than parsing a resolved plan, and the plan format is what resolves variables and counts. - Get it: The tool itself, plus a repository whose plan you can generate. - Verify: A plan output is generated and parsed into concrete resources - [ ] **Access to a provider price catalogue** · free to read - Why: Prices vary by region and change, and hard-coding a sample produces estimates that drift without breaking. - Get it: The provider's own pricing interface, and record the date you fetched. - Verify: Prices for one resource kind differ correctly between two regions - [ ] **A real infrastructure repository** · free - Why: Modules, variables and counts are where resolution actually fails, and a single-file example exercises none of it. - Get it: A repository with modules, variables and at least one resource whose cost is dominated by usage. - Verify: The repository parses with at least one unresolved and one usage-driven resource - [ ] **A real bill for that infrastructure** · free if you own the infrastructure - Why: The honesty of the coverage reporting is only assessable by comparing an estimate against what was actually charged. - Get it: A recent bill for the environment your repository describes, broken down by service. - Verify: The estimate and the bill are compared and the gap is explained by the stated coverage ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Billing reconciliation against actual usage. - Provider and resource breadth. - Commitment and discount modelling. - Any claim that an estimate predicts a bill. - AWS, Azure, and Google Cloud coverage across the full Infracost resource catalog - Terragrunt, CloudFormation, AWS CDK, modules, and complex Terraform expression coverage - custom price books, negotiated discounts, and organization-specific pricing - Issue Explorer, campaigns, AutoFix, dashboards, audit trails, and team workflows - enterprise identity, security, support, and broad CI/CD integrations If one of those is essential to you, that is the reason to keep paying for Infracost Cloud, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Infracost Cloud The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me cost estimates on infrastructure changes, honest about what they cannot include to replace Infracost Cloud. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero An infrastructure cost estimate is a lower bound on a subset, and presenting it as a monthly figure invites a decision it cannot support. What a definition file tells you is what resources exist and how they are shaped. What it cannot tell you is how much data moves between them, how many requests arrive, how much is stored after a year, or how often something scales. For a great many architectures those usage-driven costs exceed the fixed ones, and data transfer in particular is both large and entirely invisible in the definitions. So every estimate here separates what is known from what is not, states which resources have usage-driven costs that are excluded, and never presents a total without that alongside. An estimate that says forty pounds a month when the real figure is four hundred because of egress is worse than no estimate, because somebody approved it. ### Stack (fixed, do not substitute) - Parsing: Infrastructure definitions parsed to a resource graph, provider-specific · the estimate is only as good as the resource shapes you resolve - Pricing: A dated price catalogue per provider and region · prices change and a stale catalogue is a wrong estimate that looks fine - Coverage: Explicit per-resource support state, with unsupported ones counted · silently skipping a resource understates the total - Usage: Declared assumptions for usage-driven costs, never inferred · guessing egress volume produces a number nobody should trust - Reporting: Diffs against a baseline, in a pull request comment · the useful moment is before the change is merged ### Data model (create this before Phase 1) - runs(id, repository, commit_sha, base_sha, ran_at, definitions_hash) - resources(run_id, address, kind, region, attributes_json, support_state) · support_state is priced, usage_driven or unsupported - prices(provider, region, sku, unit, amount_minor, currency, effective_from, fetched_at) - estimates(run_id, resource_address, fixed_minor, usage_minor, usage_assumptions_json) - usage_profiles(id, name, assumptions_json, source) · source is declared or default, never inferred - diffs(run_id, base_run_id, resource_address, change_kind, delta_minor, coverage_changed) - An estimate stores fixed and usage-driven components separately, so a total is never reported without saying how much of the cost is not in it. ### Phase 1 · Resolving what the definitions actually create Build: Parse infrastructure definitions or a plan output into a resource graph with sizes, regions and counts resolved. Handle variables, modules and counts. Report a resource whose shape cannot be resolved rather than assuming a default. Trap: Assuming a default size for a resource whose shape depends on an unresolved variable produces an estimate for infrastructure nobody is deploying, and it looks identical to a correct one. Report unresolved shapes as unresolved and exclude them from the total rather than guessing. Done when: A real repository's definitions resolve to concrete resources with sizes and regions. Modules and counts are expanded correctly. An unresolved variable produces an unresolved resource rather than a defaulted one. The count of unresolved resources is reported alongside any total. Do not build yet: No pricing, coverage or diffs. ### Phase 2 · Prices with a date on them Build: Fetch and cache provider prices per region with the date fetched. Report the catalogue age with every estimate. Refuse to use a catalogue older than a stated threshold without an acknowledgement. Trap: A price catalogue cached indefinitely produces estimates that drift from reality without anything appearing to break, and the first sign is a bill that does not match a report from six months ago. Date the catalogue, show its age, and refuse to run silently on a stale one. Done when: Every price carries the date it was fetched and the catalogue age is shown with every estimate. A catalogue older than the threshold refuses to produce an estimate without an explicit acknowledgement. Region-specific pricing is used rather than a single default region. A price missing for a resource is reported rather than treated as zero. Do not build yet: No coverage, usage or diffs yet. ### Phase 3 · Saying what is not in the number Build: Classify each resource as priced, usage-driven or unsupported. Report the count and significance of each class alongside the total. Never omit an unsupported resource silently. Trap: Skipping resources the tool does not support, and reporting the total of the rest, produces a number that is confidently too low, and the resources most likely to be unsupported are often the expensive new ones. Count them, name them, and put the count where the total is. Done when: Every resource is classified as priced, usage-driven or unsupported and the counts appear beside the total. A resource with usage-driven costs is named as such rather than being priced at its fixed component alone. An unsupported resource is listed rather than omitted. Coverage is expressed as a proportion of resources, not of cost, since the cost of the excluded part is unknown. Do not build yet: No usage assumptions or diffs yet. ### Phase 4 · Usage assumptions, declared rather than guessed Build: Accept declared usage profiles for the resources that need them, from a file in the repository. Report the assumptions used beside the figures they produced. Never infer usage from resource shape. Trap: Inferring egress volume from an instance size produces a number with no basis that will nonetheless be quoted in a decision, and data transfer is precisely where estimates go most badly wrong. Require a declared profile, and where none exists report the resource as usage-driven with no figure rather than estimating one. Done when: Usage-driven costs appear only for resources with a declared profile. The assumptions used are printed beside every figure they produced. A resource with no declared usage is reported as usage-driven without a number rather than defaulted. Changing a usage profile changes the estimate and the change is attributable to the profile. Do not build yet: No diffs or integration yet. ### Phase 5 · The diff, where the decision happens Build: Compare a branch against its base and report the delta per resource in a pull request comment. Flag a change in coverage as well as in cost. Handle a first run with no baseline and a change that only moves resources between modules. Trap: Reporting a cost delta without reporting a coverage change lets a refactor that swaps a supported resource for an unsupported one appear as a cost reduction, when in fact the cost simply stopped being counted. Report coverage changes as prominently as cost changes. Done when: A branch reports its cost delta per resource against the base. A change that reduces coverage is flagged as prominently as a cost change, tested by replacing a supported resource with an unsupported one. A first run with no baseline reports the total rather than a delta. Moving a resource between modules does not appear as a deletion and an addition. Do not build yet: Nothing after this. ### Out of scope (and why) - Actual billing reconciliation, which requires the provider's own usage data and is a different product. - Provider and resource breadth, which is the hosted product's real value and an endless maintenance commitment. - Commitment and discount modelling, which depends on contracts the definitions know nothing about. - Any claim that an estimate predicts a bill, which it does not and cannot for usage-driven architectures. ### README must contain - That an estimate is a lower bound on a subset, since definitions cannot express usage - That every resource is classified as priced, usage-driven or unsupported and the counts appear beside the total - That usage costs require a declared profile and are never inferred from resource shape - That the price catalogue is dated and a stale one refuses to run silently - That a coverage reduction is flagged as prominently as a cost change ===== AGENTS.md ===== # Agent instructions · Infracost Cloud indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Infrastructure definitions parsed to a resource graph, provider-specific, A dated price catalogue per provider and region, Explicit per-resource support state, with unsupported ones counted, Declared assumptions for usage-driven costs, never inferred, Diffs against a baseline, in a pull request comment. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Assuming a default size for a resource whose shape depends on an unresolved variable produces an estimate for infrastructure nobody is deploying, and it looks identical to a correct one. Report unresolved shapes as unresolved and exclude them from the total rather than guessing. - A price catalogue cached indefinitely produces estimates that drift from reality without anything appearing to break, and the first sign is a bill that does not match a report from six months ago. Date the catalogue, show its age, and refuse to run silently on a stale one. - Skipping resources the tool does not support, and reporting the total of the rest, produces a number that is confidently too low, and the resources most likely to be unsupported are often the expensive new ones. Count them, name them, and put the count where the total is. - Inferring egress volume from an instance size produces a number with no basis that will nonetheless be quoted in a decision, and data transfer is precisely where estimates go most badly wrong. Require a declared profile, and where none exists report the resource as usage-driven with no figure rather than estimating one. ===== BUILD_PLAN.md ===== # Build plan · Infracost Cloud A cost estimation tool for infrastructure changes whose value depends entirely on being honest about coverage: the fixed costs are computable from the definitions, the usage-driven ones are not, and conflating them produces confident wrong numbers people act on. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Resolving what the definitions actually create A plan resolves to concrete resources with their shapes. ### Steps 1. Resolve shapes, do not default them A defaulted size is an estimate for infrastructure nobody is deploying. Files: `src/parse.ts` 2. Expand modules and counts Where the resource count actually comes from. Files: `src/expand.ts` 3. Report unresolved resources Alongside the total, not in a log. Files: `src/report.ts` ### Done when - [ ] Definitions resolve to concrete resources - [ ] Modules and counts expand correctly - [ ] Unresolved variables produce unresolved resources - [ ] Unresolved counts appear with the total ### Watch out - Assuming a default size for a resource whose shape depends on an unresolved variable produces an estimate for infrastructure nobody is deploying, and it looks identical to a correct one. Report unresolved shapes as unresolved and exclude them from the total rather than guessing. ## Phase 2 · Prices with a date on them Every price used is dated and its staleness is visible. ### Steps 1. Date the catalogue and show its age A stale catalogue is a wrong estimate that looks entirely normal. Files: `src/prices.ts` 2. Use region-specific prices The same resource differs substantially between regions. Files: `src/prices.ts` 3. Report missing prices as missing Treating an absent price as zero understates the total silently. Files: `src/estimate.ts` ### Done when - [ ] Prices carry fetch dates and the age is shown - [ ] A stale catalogue refuses without acknowledgement - [ ] Region-specific pricing is used - [ ] Missing prices are reported rather than zeroed ### Watch out - A price catalogue cached indefinitely produces estimates that drift from reality without anything appearing to break, and the first sign is a bill that does not match a report from six months ago. Date the catalogue, show its age, and refuse to run silently on a stale one. ## Phase 3 · Saying what is not in the number Every estimate states its coverage explicitly. ### Steps 1. Classify every resource Priced, usage-driven, unsupported. Three states, all reported. Files: `src/coverage.ts` 2. Put coverage beside the total Not in a footnote. It is what makes the total interpretable. Files: `src/report.ts` 3. Express coverage by resource count By cost is impossible, since the excluded cost is what you do not know. Files: `src/coverage.ts` ### Done when - [ ] Every resource is classified into three states - [ ] Coverage appears beside the total - [ ] Usage-driven resources are named - [ ] Coverage is expressed by resource count ### Watch out - Skipping resources the tool does not support, and reporting the total of the rest, produces a number that is confidently too low, and the resources most likely to be unsupported are often the expensive new ones. Count them, name them, and put the count where the total is. ## Phase 4 · Usage assumptions, declared rather than guessed Usage-driven costs appear only where someone stated the usage. ### Steps 1. Take usage from a declared file In the repository, reviewable alongside the change. Files: `src/usage.ts` 2. Print assumptions with the figures So a wrong number is traceable to a wrong assumption. Files: `src/report.ts` 3. Never infer usage from shape Egress from instance size is a number with no basis. Files: `tests/no-inference.test.ts` ### Done when - [ ] Usage costs require a declared profile - [ ] Assumptions print beside their figures - [ ] Undeclared usage produces no number - [ ] Profile changes are attributable ### Watch out - Inferring egress volume from an instance size produces a number with no basis that will nonetheless be quoted in a decision, and data transfer is precisely where estimates go most badly wrong. Require a declared profile, and where none exists report the resource as usage-driven with no figure rather than estimating one. ## Not in this build - Billing reconciliation against actual usage. - Provider and resource breadth. - Commitment and discount modelling. - Any claim that an estimate predicts a bill. ## After v1, if you want it - Comparing estimates against the eventual bill, which is the only way to calibrate coverage honestly - Policy checks such as refusing a resource kind, which the resource graph already supports - Usage profiles derived from the previous month's actual usage, declared rather than inferred ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Dated per fetch. A stale catalogue refuses to run silently. PRICE_CACHE_PATH=./cache/prices.json # Required. Beyond this an estimate requires an explicit acknowledgement. PRICE_MAX_AGE_DAYS=14 # Required. Declared usage. Absent means usage-driven resources report no figure. USAGE_PROFILE_PATH=./infracost-usage.yml # Required. Present as a constant. Inferred egress is a number with no basis. INFER_USAGE=never # Required. Present as a constant. A total without coverage is not interpretable. REPORT_COVERAGE=always # Required. Used only where a resource does not state one, and reported when it is. DEFAULT_REGION=eu-west-1
You are building a production product version of Infracost Cloud. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Infracost Cloud · product brief ## Problem The core developer loop is a credible weekend build if the replacement is intentionally narrow: one GitHub App, Terraform on AWS, a small resource catalog, public list prices, basic tag and cost guardrails, and one idempotent pull-request comment. A faithful Infracost Cloud replacement is much harder because the paid product combines broad IaC and multi-cloud coverage with continuously maintained pricing data, policy depth, custom price books, organization-wide visibility, workflows, and enterprise integrations. ## Product outcome A cost estimate on a pull request that says what it counted, what it could not, and which resources will cost whatever their usage turns out to be. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - GitHub App credentials and a public HTTPS webhook endpoint - Docker with persistent storage for SQLite - Internet access to GitHub and the public AWS Price List files ## Explicit non-goals for v1 - Billing reconciliation against actual usage. - Provider and resource breadth. - Commitment and discount modelling. - Any claim that an estimate predicts a bill. - AWS, Azure, and Google Cloud coverage across the full Infracost resource catalog - Terragrunt, CloudFormation, AWS CDK, modules, and complex Terraform expression coverage - custom price books, negotiated discounts, and organization-specific pricing - Issue Explorer, campaigns, AutoFix, dashboards, audit trails, and team workflows - enterprise identity, security, support, and broad CI/CD integrations ## Success criteria - Unresolved variables are confirmed to produce unresolved resources - A stale catalogue is confirmed to refuse without acknowledgement - Coverage counts are confirmed to appear beside the total - Undeclared usage is confirmed to produce no figure - A coverage reduction is confirmed flagged prominently ===== BRIEF.md ===== # Build brief · Infracost Cloud The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me cost estimates on infrastructure changes, honest about what they cannot include to replace Infracost Cloud. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero An infrastructure cost estimate is a lower bound on a subset, and presenting it as a monthly figure invites a decision it cannot support. What a definition file tells you is what resources exist and how they are shaped. What it cannot tell you is how much data moves between them, how many requests arrive, how much is stored after a year, or how often something scales. For a great many architectures those usage-driven costs exceed the fixed ones, and data transfer in particular is both large and entirely invisible in the definitions. So every estimate here separates what is known from what is not, states which resources have usage-driven costs that are excluded, and never presents a total without that alongside. An estimate that says forty pounds a month when the real figure is four hundred because of egress is worse than no estimate, because somebody approved it. ### Stack (fixed, do not substitute) - Parsing: Infrastructure definitions parsed to a resource graph, provider-specific · the estimate is only as good as the resource shapes you resolve - Pricing: A dated price catalogue per provider and region · prices change and a stale catalogue is a wrong estimate that looks fine - Coverage: Explicit per-resource support state, with unsupported ones counted · silently skipping a resource understates the total - Usage: Declared assumptions for usage-driven costs, never inferred · guessing egress volume produces a number nobody should trust - Reporting: Diffs against a baseline, in a pull request comment · the useful moment is before the change is merged ### Data model (create this before Phase 1) - runs(id, repository, commit_sha, base_sha, ran_at, definitions_hash) - resources(run_id, address, kind, region, attributes_json, support_state) · support_state is priced, usage_driven or unsupported - prices(provider, region, sku, unit, amount_minor, currency, effective_from, fetched_at) - estimates(run_id, resource_address, fixed_minor, usage_minor, usage_assumptions_json) - usage_profiles(id, name, assumptions_json, source) · source is declared or default, never inferred - diffs(run_id, base_run_id, resource_address, change_kind, delta_minor, coverage_changed) - An estimate stores fixed and usage-driven components separately, so a total is never reported without saying how much of the cost is not in it. ### Phase 1 · Resolving what the definitions actually create Build: Parse infrastructure definitions or a plan output into a resource graph with sizes, regions and counts resolved. Handle variables, modules and counts. Report a resource whose shape cannot be resolved rather than assuming a default. Trap: Assuming a default size for a resource whose shape depends on an unresolved variable produces an estimate for infrastructure nobody is deploying, and it looks identical to a correct one. Report unresolved shapes as unresolved and exclude them from the total rather than guessing. Done when: A real repository's definitions resolve to concrete resources with sizes and regions. Modules and counts are expanded correctly. An unresolved variable produces an unresolved resource rather than a defaulted one. The count of unresolved resources is reported alongside any total. Do not build yet: No pricing, coverage or diffs. ### Phase 2 · Prices with a date on them Build: Fetch and cache provider prices per region with the date fetched. Report the catalogue age with every estimate. Refuse to use a catalogue older than a stated threshold without an acknowledgement. Trap: A price catalogue cached indefinitely produces estimates that drift from reality without anything appearing to break, and the first sign is a bill that does not match a report from six months ago. Date the catalogue, show its age, and refuse to run silently on a stale one. Done when: Every price carries the date it was fetched and the catalogue age is shown with every estimate. A catalogue older than the threshold refuses to produce an estimate without an explicit acknowledgement. Region-specific pricing is used rather than a single default region. A price missing for a resource is reported rather than treated as zero. Do not build yet: No coverage, usage or diffs yet. ### Phase 3 · Saying what is not in the number Build: Classify each resource as priced, usage-driven or unsupported. Report the count and significance of each class alongside the total. Never omit an unsupported resource silently. Trap: Skipping resources the tool does not support, and reporting the total of the rest, produces a number that is confidently too low, and the resources most likely to be unsupported are often the expensive new ones. Count them, name them, and put the count where the total is. Done when: Every resource is classified as priced, usage-driven or unsupported and the counts appear beside the total. A resource with usage-driven costs is named as such rather than being priced at its fixed component alone. An unsupported resource is listed rather than omitted. Coverage is expressed as a proportion of resources, not of cost, since the cost of the excluded part is unknown. Do not build yet: No usage assumptions or diffs yet. ### Phase 4 · Usage assumptions, declared rather than guessed Build: Accept declared usage profiles for the resources that need them, from a file in the repository. Report the assumptions used beside the figures they produced. Never infer usage from resource shape. Trap: Inferring egress volume from an instance size produces a number with no basis that will nonetheless be quoted in a decision, and data transfer is precisely where estimates go most badly wrong. Require a declared profile, and where none exists report the resource as usage-driven with no figure rather than estimating one. Done when: Usage-driven costs appear only for resources with a declared profile. The assumptions used are printed beside every figure they produced. A resource with no declared usage is reported as usage-driven without a number rather than defaulted. Changing a usage profile changes the estimate and the change is attributable to the profile. Do not build yet: No diffs or integration yet. ### Phase 5 · The diff, where the decision happens Build: Compare a branch against its base and report the delta per resource in a pull request comment. Flag a change in coverage as well as in cost. Handle a first run with no baseline and a change that only moves resources between modules. Trap: Reporting a cost delta without reporting a coverage change lets a refactor that swaps a supported resource for an unsupported one appear as a cost reduction, when in fact the cost simply stopped being counted. Report coverage changes as prominently as cost changes. Done when: A branch reports its cost delta per resource against the base. A change that reduces coverage is flagged as prominently as a cost change, tested by replacing a supported resource with an unsupported one. A first run with no baseline reports the total rather than a delta. Moving a resource between modules does not appear as a deletion and an addition. Do not build yet: Nothing after this. ### Out of scope (and why) - Actual billing reconciliation, which requires the provider's own usage data and is a different product. - Provider and resource breadth, which is the hosted product's real value and an endless maintenance commitment. - Commitment and discount modelling, which depends on contracts the definitions know nothing about. - Any claim that an estimate predicts a bill, which it does not and cannot for usage-driven architectures. ### README must contain - That an estimate is a lower bound on a subset, since definitions cannot express usage - That every resource is classified as priced, usage-driven or unsupported and the counts appear beside the total - That usage costs require a declared profile and are never inferred from resource shape - That the price catalogue is dated and a stale one refuses to run silently - That a coverage reduction is flagged as prominently as a cost change ===== ARCHITECTURE.md ===== # Architecture · Infracost Cloud ## Stack | Part | Choice | Why | | --- | --- | --- | | Parsing | Infrastructure definitions parsed to a resource graph, provider-specific | the estimate is only as good as the resource shapes you resolve | | Pricing | A dated price catalogue per provider and region | prices change and a stale catalogue is a wrong estimate that looks fine | | Coverage | Explicit per-resource support state, with unsupported ones counted | silently skipping a resource understates the total | | Usage | Declared assumptions for usage-driven costs, never inferred | guessing egress volume produces a number nobody should trust | | Reporting | Diffs against a baseline, in a pull request comment | the useful moment is before the change is merged | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Parsing | Resolving definitions to concrete resources. | Any provider, provided unresolved shapes are reported rather than defaulted. | | Pricing | Dated regional catalogues. | Any source, provided the catalogue age is visible and staleness refuses. | | Coverage | Three-state classification reported with totals. | Not swappable for silent omission, which understates confidently. | | Usage | Declared profiles only. | Any format, provided nothing is inferred from resource shape. | | Diff | Per-resource deltas with coverage changes. | Any presentation, provided coverage changes are as prominent as cost. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `PRICE_CACHE_PATH` · required · Dated per fetch. A stale catalogue refuses to run silently. - `PRICE_MAX_AGE_DAYS` · required · Beyond this an estimate requires an explicit acknowledgement. - `USAGE_PROFILE_PATH` · required · Declared usage. Absent means usage-driven resources report no figure. - `INFER_USAGE` · required · Present as a constant. Inferred egress is a number with no basis. - `REPORT_COVERAGE` · required · Present as a constant. A total without coverage is not interpretable. - `DEFAULT_REGION` · required · Used only where a resource does not state one, and reported when it is. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Infracost Cloud product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Infrastructure definitions parsed to a resource graph, provider-specific, A dated price catalogue per provider and region, Explicit per-resource support state, with unsupported ones counted, Declared assumptions for usage-driven costs, never inferred, Diffs against a baseline, in a pull request comment. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Assuming a default size for a resource whose shape depends on an unresolved variable produces an estimate for infrastructure nobody is deploying, and it looks identical to a correct one. Report unresolved shapes as unresolved and exclude them from the total rather than guessing. - A price catalogue cached indefinitely produces estimates that drift from reality without anything appearing to break, and the first sign is a bill that does not match a report from six months ago. Date the catalogue, show its age, and refuse to run silently on a stale one. - Skipping resources the tool does not support, and reporting the total of the rest, produces a number that is confidently too low, and the resources most likely to be unsupported are often the expensive new ones. Count them, name them, and put the count where the total is. - Inferring egress volume from an instance size produces a number with no basis that will nonetheless be quoted in a decision, and data transfer is precisely where estimates go most badly wrong. Require a declared profile, and where none exists report the resource as usage-driven with no figure rather than estimating one. - Reporting a cost delta without reporting a coverage change lets a refactor that swaps a supported resource for an unsupported one appear as a cost reduction, when in fact the cost simply stopped being counted. Report coverage changes as prominently as cost changes. ===== MILESTONES.md ===== # Delivery milestones · Infracost Cloud Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Resolving what the definitions actually create A plan resolves to concrete resources with their shapes. ### Steps 1. Resolve shapes, do not default them A defaulted size is an estimate for infrastructure nobody is deploying. Files: `src/parse.ts` 2. Expand modules and counts Where the resource count actually comes from. Files: `src/expand.ts` 3. Report unresolved resources Alongside the total, not in a log. Files: `src/report.ts` ### Done when - [ ] Definitions resolve to concrete resources - [ ] Modules and counts expand correctly - [ ] Unresolved variables produce unresolved resources - [ ] Unresolved counts appear with the total ### Watch out - Assuming a default size for a resource whose shape depends on an unresolved variable produces an estimate for infrastructure nobody is deploying, and it looks identical to a correct one. Report unresolved shapes as unresolved and exclude them from the total rather than guessing. ## M2 · Prices with a date on them Every price used is dated and its staleness is visible. ### Steps 1. Date the catalogue and show its age A stale catalogue is a wrong estimate that looks entirely normal. Files: `src/prices.ts` 2. Use region-specific prices The same resource differs substantially between regions. Files: `src/prices.ts` 3. Report missing prices as missing Treating an absent price as zero understates the total silently. Files: `src/estimate.ts` ### Done when - [ ] Prices carry fetch dates and the age is shown - [ ] A stale catalogue refuses without acknowledgement - [ ] Region-specific pricing is used - [ ] Missing prices are reported rather than zeroed ### Watch out - A price catalogue cached indefinitely produces estimates that drift from reality without anything appearing to break, and the first sign is a bill that does not match a report from six months ago. Date the catalogue, show its age, and refuse to run silently on a stale one. ## M3 · Saying what is not in the number Every estimate states its coverage explicitly. ### Steps 1. Classify every resource Priced, usage-driven, unsupported. Three states, all reported. Files: `src/coverage.ts` 2. Put coverage beside the total Not in a footnote. It is what makes the total interpretable. Files: `src/report.ts` 3. Express coverage by resource count By cost is impossible, since the excluded cost is what you do not know. Files: `src/coverage.ts` ### Done when - [ ] Every resource is classified into three states - [ ] Coverage appears beside the total - [ ] Usage-driven resources are named - [ ] Coverage is expressed by resource count ### Watch out - Skipping resources the tool does not support, and reporting the total of the rest, produces a number that is confidently too low, and the resources most likely to be unsupported are often the expensive new ones. Count them, name them, and put the count where the total is. ## M4 · Usage assumptions, declared rather than guessed Usage-driven costs appear only where someone stated the usage. ### Steps 1. Take usage from a declared file In the repository, reviewable alongside the change. Files: `src/usage.ts` 2. Print assumptions with the figures So a wrong number is traceable to a wrong assumption. Files: `src/report.ts` 3. Never infer usage from shape Egress from instance size is a number with no basis. Files: `tests/no-inference.test.ts` ### Done when - [ ] Usage costs require a declared profile - [ ] Assumptions print beside their figures - [ ] Undeclared usage produces no number - [ ] Profile changes are attributable ### Watch out - Inferring egress volume from an instance size produces a number with no basis that will nonetheless be quoted in a decision, and data transfer is precisely where estimates go most badly wrong. Require a declared profile, and where none exists report the resource as usage-driven with no figure rather than estimating one. ## M5 · The diff, where the decision happens (production only) A change shows its cost delta before it is merged. ### Steps 1. Diff per resource against the base The total delta hides the resource that changed. Files: `src/diff.ts` 2. Flag coverage changes prominently A refactor to an unsupported resource looks like a saving otherwise. Files: `src/diff.ts`, `tests/coverage-change.test.ts` 3. Match resources across moves A module rename should not read as deleting everything. Files: `src/matching.ts` ### Done when - [ ] Deltas are reported per resource - [ ] Coverage reductions are flagged prominently - [ ] A first run reports a total rather than a delta - [ ] Module moves do not read as delete and add ### Watch out - Reporting a cost delta without reporting a coverage change lets a refactor that swaps a supported resource for an unsupported one appear as a cost reduction, when in fact the cost simply stopped being counted. Report coverage changes as prominently as cost changes. ===== OPERATIONS.md ===== # Operations · Infracost Cloud ## Backup The price catalogue is regenerable and the estimates are derived. Usage profiles live in the repository with the code they describe, which is where they belong and where they are backed up already. ## Restore Restore and confirm the price catalogue is refreshed rather than restored stale, since an old catalogue produces estimates that look current and are not. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Catalogue age and the proportion of unsupported resources. The second grows quietly as teams adopt newer services, and a falling coverage figure is the leading indicator of estimates becoming useless. ## Incident checklist An approved change whose bill was many times the estimate is the failure that matters, and it is nearly always egress or another usage-driven cost the estimate correctly excluded but nobody read. That is why coverage sits beside the total rather than beneath it. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Unresolved variables are confirmed to produce unresolved resources - [ ] A stale catalogue is confirmed to refuse without acknowledgement - [ ] Coverage counts are confirmed to appear beside the total - [ ] Undeclared usage is confirmed to produce no figure - [ ] A coverage reduction is confirmed flagged prominently ## Launch constraint Do not market omitted Infracost Cloud capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Dated per fetch. A stale catalogue refuses to run silently. PRICE_CACHE_PATH=./cache/prices.json # Required. Beyond this an estimate requires an explicit acknowledgement. PRICE_MAX_AGE_DAYS=14 # Required. Declared usage. Absent means usage-driven resources report no figure. USAGE_PROFILE_PATH=./infracost-usage.yml # Required. Present as a constant. Inferred egress is a number with no basis. INFER_USAGE=never # Required. Present as a constant. A total without coverage is not interpretable. REPORT_COVERAGE=always # Required. Used only where a resource does not state one, and reported when it is. DEFAULT_REGION=eu-west-1
# Infracost Cloud · indie build A cost estimation tool for infrastructure changes whose value depends entirely on being honest about coverage: the fixed costs are computable from the definitions, the usage-driven ones are not, and conflating them produces confident wrong numbers people act on. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Parsing | Infrastructure definitions parsed to a resource graph, provider-specific | the estimate is only as good as the resource shapes you resolve | | Pricing | A dated price catalogue per provider and region | prices change and a stale catalogue is a wrong estimate that looks fine | | Coverage | Explicit per-resource support state, with unsupported ones counted | silently skipping a resource understates the total | | Usage | Declared assumptions for usage-driven costs, never inferred | guessing egress volume produces a number nobody should trust | | Reporting | Diffs against a baseline, in a pull request comment | the useful moment is before the change is merged | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the parser, the pricing layer and the reporting. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **The infrastructure tool whose definitions you will parse** · free - Why: Parsing the source text is far less reliable than parsing a resolved plan, and the plan format is what resolves variables and counts. - Get it: The tool itself, plus a repository whose plan you can generate. - Verify: A plan output is generated and parsed into concrete resources - [ ] **Access to a provider price catalogue** · free to read - Why: Prices vary by region and change, and hard-coding a sample produces estimates that drift without breaking. - Get it: The provider's own pricing interface, and record the date you fetched. - Verify: Prices for one resource kind differ correctly between two regions - [ ] **A real infrastructure repository** · free - Why: Modules, variables and counts are where resolution actually fails, and a single-file example exercises none of it. - Get it: A repository with modules, variables and at least one resource whose cost is dominated by usage. - Verify: The repository parses with at least one unresolved and one usage-driven resource - [ ] **A real bill for that infrastructure** · free if you own the infrastructure - Why: The honesty of the coverage reporting is only assessable by comparing an estimate against what was actually charged. - Get it: A recent bill for the environment your repository describes, broken down by service. - Verify: The estimate and the bill are compared and the gap is explained by the stated coverage ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Billing reconciliation against actual usage. - Provider and resource breadth. - Commitment and discount modelling. - Any claim that an estimate predicts a bill. - AWS, Azure, and Google Cloud coverage across the full Infracost resource catalog - Terragrunt, CloudFormation, AWS CDK, modules, and complex Terraform expression coverage - custom price books, negotiated discounts, and organization-specific pricing - Issue Explorer, campaigns, AutoFix, dashboards, audit trails, and team workflows - enterprise identity, security, support, and broad CI/CD integrations If one of those is essential to you, that is the reason to keep paying for Infracost Cloud, and the README should say so rather than pretend.
# Build brief · Infracost Cloud The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me cost estimates on infrastructure changes, honest about what they cannot include to replace Infracost Cloud. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero An infrastructure cost estimate is a lower bound on a subset, and presenting it as a monthly figure invites a decision it cannot support. What a definition file tells you is what resources exist and how they are shaped. What it cannot tell you is how much data moves between them, how many requests arrive, how much is stored after a year, or how often something scales. For a great many architectures those usage-driven costs exceed the fixed ones, and data transfer in particular is both large and entirely invisible in the definitions. So every estimate here separates what is known from what is not, states which resources have usage-driven costs that are excluded, and never presents a total without that alongside. An estimate that says forty pounds a month when the real figure is four hundred because of egress is worse than no estimate, because somebody approved it. ### Stack (fixed, do not substitute) - Parsing: Infrastructure definitions parsed to a resource graph, provider-specific · the estimate is only as good as the resource shapes you resolve - Pricing: A dated price catalogue per provider and region · prices change and a stale catalogue is a wrong estimate that looks fine - Coverage: Explicit per-resource support state, with unsupported ones counted · silently skipping a resource understates the total - Usage: Declared assumptions for usage-driven costs, never inferred · guessing egress volume produces a number nobody should trust - Reporting: Diffs against a baseline, in a pull request comment · the useful moment is before the change is merged ### Data model (create this before Phase 1) - runs(id, repository, commit_sha, base_sha, ran_at, definitions_hash) - resources(run_id, address, kind, region, attributes_json, support_state) · support_state is priced, usage_driven or unsupported - prices(provider, region, sku, unit, amount_minor, currency, effective_from, fetched_at) - estimates(run_id, resource_address, fixed_minor, usage_minor, usage_assumptions_json) - usage_profiles(id, name, assumptions_json, source) · source is declared or default, never inferred - diffs(run_id, base_run_id, resource_address, change_kind, delta_minor, coverage_changed) - An estimate stores fixed and usage-driven components separately, so a total is never reported without saying how much of the cost is not in it. ### Phase 1 · Resolving what the definitions actually create Build: Parse infrastructure definitions or a plan output into a resource graph with sizes, regions and counts resolved. Handle variables, modules and counts. Report a resource whose shape cannot be resolved rather than assuming a default. Trap: Assuming a default size for a resource whose shape depends on an unresolved variable produces an estimate for infrastructure nobody is deploying, and it looks identical to a correct one. Report unresolved shapes as unresolved and exclude them from the total rather than guessing. Done when: A real repository's definitions resolve to concrete resources with sizes and regions. Modules and counts are expanded correctly. An unresolved variable produces an unresolved resource rather than a defaulted one. The count of unresolved resources is reported alongside any total. Do not build yet: No pricing, coverage or diffs. ### Phase 2 · Prices with a date on them Build: Fetch and cache provider prices per region with the date fetched. Report the catalogue age with every estimate. Refuse to use a catalogue older than a stated threshold without an acknowledgement. Trap: A price catalogue cached indefinitely produces estimates that drift from reality without anything appearing to break, and the first sign is a bill that does not match a report from six months ago. Date the catalogue, show its age, and refuse to run silently on a stale one. Done when: Every price carries the date it was fetched and the catalogue age is shown with every estimate. A catalogue older than the threshold refuses to produce an estimate without an explicit acknowledgement. Region-specific pricing is used rather than a single default region. A price missing for a resource is reported rather than treated as zero. Do not build yet: No coverage, usage or diffs yet. ### Phase 3 · Saying what is not in the number Build: Classify each resource as priced, usage-driven or unsupported. Report the count and significance of each class alongside the total. Never omit an unsupported resource silently. Trap: Skipping resources the tool does not support, and reporting the total of the rest, produces a number that is confidently too low, and the resources most likely to be unsupported are often the expensive new ones. Count them, name them, and put the count where the total is. Done when: Every resource is classified as priced, usage-driven or unsupported and the counts appear beside the total. A resource with usage-driven costs is named as such rather than being priced at its fixed component alone. An unsupported resource is listed rather than omitted. Coverage is expressed as a proportion of resources, not of cost, since the cost of the excluded part is unknown. Do not build yet: No usage assumptions or diffs yet. ### Phase 4 · Usage assumptions, declared rather than guessed Build: Accept declared usage profiles for the resources that need them, from a file in the repository. Report the assumptions used beside the figures they produced. Never infer usage from resource shape. Trap: Inferring egress volume from an instance size produces a number with no basis that will nonetheless be quoted in a decision, and data transfer is precisely where estimates go most badly wrong. Require a declared profile, and where none exists report the resource as usage-driven with no figure rather than estimating one. Done when: Usage-driven costs appear only for resources with a declared profile. The assumptions used are printed beside every figure they produced. A resource with no declared usage is reported as usage-driven without a number rather than defaulted. Changing a usage profile changes the estimate and the change is attributable to the profile. Do not build yet: No diffs or integration yet. ### Phase 5 · The diff, where the decision happens Build: Compare a branch against its base and report the delta per resource in a pull request comment. Flag a change in coverage as well as in cost. Handle a first run with no baseline and a change that only moves resources between modules. Trap: Reporting a cost delta without reporting a coverage change lets a refactor that swaps a supported resource for an unsupported one appear as a cost reduction, when in fact the cost simply stopped being counted. Report coverage changes as prominently as cost changes. Done when: A branch reports its cost delta per resource against the base. A change that reduces coverage is flagged as prominently as a cost change, tested by replacing a supported resource with an unsupported one. A first run with no baseline reports the total rather than a delta. Moving a resource between modules does not appear as a deletion and an addition. Do not build yet: Nothing after this. ### Out of scope (and why) - Actual billing reconciliation, which requires the provider's own usage data and is a different product. - Provider and resource breadth, which is the hosted product's real value and an endless maintenance commitment. - Commitment and discount modelling, which depends on contracts the definitions know nothing about. - Any claim that an estimate predicts a bill, which it does not and cannot for usage-driven architectures. ### README must contain - That an estimate is a lower bound on a subset, since definitions cannot express usage - That every resource is classified as priced, usage-driven or unsupported and the counts appear beside the total - That usage costs require a declared profile and are never inferred from resource shape - That the price catalogue is dated and a stale one refuses to run silently - That a coverage reduction is flagged as prominently as a cost change
# Agent instructions · Infracost Cloud indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Infrastructure definitions parsed to a resource graph, provider-specific, A dated price catalogue per provider and region, Explicit per-resource support state, with unsupported ones counted, Declared assumptions for usage-driven costs, never inferred, Diffs against a baseline, in a pull request comment. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Assuming a default size for a resource whose shape depends on an unresolved variable produces an estimate for infrastructure nobody is deploying, and it looks identical to a correct one. Report unresolved shapes as unresolved and exclude them from the total rather than guessing. - A price catalogue cached indefinitely produces estimates that drift from reality without anything appearing to break, and the first sign is a bill that does not match a report from six months ago. Date the catalogue, show its age, and refuse to run silently on a stale one. - Skipping resources the tool does not support, and reporting the total of the rest, produces a number that is confidently too low, and the resources most likely to be unsupported are often the expensive new ones. Count them, name them, and put the count where the total is. - Inferring egress volume from an instance size produces a number with no basis that will nonetheless be quoted in a decision, and data transfer is precisely where estimates go most badly wrong. Require a declared profile, and where none exists report the resource as usage-driven with no figure rather than estimating one.
# Build plan · Infracost Cloud A cost estimation tool for infrastructure changes whose value depends entirely on being honest about coverage: the fixed costs are computable from the definitions, the usage-driven ones are not, and conflating them produces confident wrong numbers people act on. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Resolving what the definitions actually create A plan resolves to concrete resources with their shapes. ### Steps 1. Resolve shapes, do not default them A defaulted size is an estimate for infrastructure nobody is deploying. Files: `src/parse.ts` 2. Expand modules and counts Where the resource count actually comes from. Files: `src/expand.ts` 3. Report unresolved resources Alongside the total, not in a log. Files: `src/report.ts` ### Done when - [ ] Definitions resolve to concrete resources - [ ] Modules and counts expand correctly - [ ] Unresolved variables produce unresolved resources - [ ] Unresolved counts appear with the total ### Watch out - Assuming a default size for a resource whose shape depends on an unresolved variable produces an estimate for infrastructure nobody is deploying, and it looks identical to a correct one. Report unresolved shapes as unresolved and exclude them from the total rather than guessing. ## Phase 2 · Prices with a date on them Every price used is dated and its staleness is visible. ### Steps 1. Date the catalogue and show its age A stale catalogue is a wrong estimate that looks entirely normal. Files: `src/prices.ts` 2. Use region-specific prices The same resource differs substantially between regions. Files: `src/prices.ts` 3. Report missing prices as missing Treating an absent price as zero understates the total silently. Files: `src/estimate.ts` ### Done when - [ ] Prices carry fetch dates and the age is shown - [ ] A stale catalogue refuses without acknowledgement - [ ] Region-specific pricing is used - [ ] Missing prices are reported rather than zeroed ### Watch out - A price catalogue cached indefinitely produces estimates that drift from reality without anything appearing to break, and the first sign is a bill that does not match a report from six months ago. Date the catalogue, show its age, and refuse to run silently on a stale one. ## Phase 3 · Saying what is not in the number Every estimate states its coverage explicitly. ### Steps 1. Classify every resource Priced, usage-driven, unsupported. Three states, all reported. Files: `src/coverage.ts` 2. Put coverage beside the total Not in a footnote. It is what makes the total interpretable. Files: `src/report.ts` 3. Express coverage by resource count By cost is impossible, since the excluded cost is what you do not know. Files: `src/coverage.ts` ### Done when - [ ] Every resource is classified into three states - [ ] Coverage appears beside the total - [ ] Usage-driven resources are named - [ ] Coverage is expressed by resource count ### Watch out - Skipping resources the tool does not support, and reporting the total of the rest, produces a number that is confidently too low, and the resources most likely to be unsupported are often the expensive new ones. Count them, name them, and put the count where the total is. ## Phase 4 · Usage assumptions, declared rather than guessed Usage-driven costs appear only where someone stated the usage. ### Steps 1. Take usage from a declared file In the repository, reviewable alongside the change. Files: `src/usage.ts` 2. Print assumptions with the figures So a wrong number is traceable to a wrong assumption. Files: `src/report.ts` 3. Never infer usage from shape Egress from instance size is a number with no basis. Files: `tests/no-inference.test.ts` ### Done when - [ ] Usage costs require a declared profile - [ ] Assumptions print beside their figures - [ ] Undeclared usage produces no number - [ ] Profile changes are attributable ### Watch out - Inferring egress volume from an instance size produces a number with no basis that will nonetheless be quoted in a decision, and data transfer is precisely where estimates go most badly wrong. Require a declared profile, and where none exists report the resource as usage-driven with no figure rather than estimating one. ## Not in this build - Billing reconciliation against actual usage. - Provider and resource breadth. - Commitment and discount modelling. - Any claim that an estimate predicts a bill. ## After v1, if you want it - Comparing estimates against the eventual bill, which is the only way to calibrate coverage honestly - Policy checks such as refusing a resource kind, which the resource graph already supports - Usage profiles derived from the previous month's actual usage, declared rather than inferred
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Dated per fetch. A stale catalogue refuses to run silently. PRICE_CACHE_PATH=./cache/prices.json # Required. Beyond this an estimate requires an explicit acknowledgement. PRICE_MAX_AGE_DAYS=14 # Required. Declared usage. Absent means usage-driven resources report no figure. USAGE_PROFILE_PATH=./infracost-usage.yml # Required. Present as a constant. Inferred egress is a number with no basis. INFER_USAGE=never # Required. Present as a constant. A total without coverage is not interpretable. REPORT_COVERAGE=always # Required. Used only where a resource does not state one, and reported when it is. DEFAULT_REGION=eu-west-1
# Infracost Cloud · product brief ## Problem The core developer loop is a credible weekend build if the replacement is intentionally narrow: one GitHub App, Terraform on AWS, a small resource catalog, public list prices, basic tag and cost guardrails, and one idempotent pull-request comment. A faithful Infracost Cloud replacement is much harder because the paid product combines broad IaC and multi-cloud coverage with continuously maintained pricing data, policy depth, custom price books, organization-wide visibility, workflows, and enterprise integrations. ## Product outcome A cost estimate on a pull request that says what it counted, what it could not, and which resources will cost whatever their usage turns out to be. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - GitHub App credentials and a public HTTPS webhook endpoint - Docker with persistent storage for SQLite - Internet access to GitHub and the public AWS Price List files ## Explicit non-goals for v1 - Billing reconciliation against actual usage. - Provider and resource breadth. - Commitment and discount modelling. - Any claim that an estimate predicts a bill. - AWS, Azure, and Google Cloud coverage across the full Infracost resource catalog - Terragrunt, CloudFormation, AWS CDK, modules, and complex Terraform expression coverage - custom price books, negotiated discounts, and organization-specific pricing - Issue Explorer, campaigns, AutoFix, dashboards, audit trails, and team workflows - enterprise identity, security, support, and broad CI/CD integrations ## Success criteria - Unresolved variables are confirmed to produce unresolved resources - A stale catalogue is confirmed to refuse without acknowledgement - Coverage counts are confirmed to appear beside the total - Undeclared usage is confirmed to produce no figure - A coverage reduction is confirmed flagged prominently
# Build brief · Infracost Cloud The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me cost estimates on infrastructure changes, honest about what they cannot include to replace Infracost Cloud. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero An infrastructure cost estimate is a lower bound on a subset, and presenting it as a monthly figure invites a decision it cannot support. What a definition file tells you is what resources exist and how they are shaped. What it cannot tell you is how much data moves between them, how many requests arrive, how much is stored after a year, or how often something scales. For a great many architectures those usage-driven costs exceed the fixed ones, and data transfer in particular is both large and entirely invisible in the definitions. So every estimate here separates what is known from what is not, states which resources have usage-driven costs that are excluded, and never presents a total without that alongside. An estimate that says forty pounds a month when the real figure is four hundred because of egress is worse than no estimate, because somebody approved it. ### Stack (fixed, do not substitute) - Parsing: Infrastructure definitions parsed to a resource graph, provider-specific · the estimate is only as good as the resource shapes you resolve - Pricing: A dated price catalogue per provider and region · prices change and a stale catalogue is a wrong estimate that looks fine - Coverage: Explicit per-resource support state, with unsupported ones counted · silently skipping a resource understates the total - Usage: Declared assumptions for usage-driven costs, never inferred · guessing egress volume produces a number nobody should trust - Reporting: Diffs against a baseline, in a pull request comment · the useful moment is before the change is merged ### Data model (create this before Phase 1) - runs(id, repository, commit_sha, base_sha, ran_at, definitions_hash) - resources(run_id, address, kind, region, attributes_json, support_state) · support_state is priced, usage_driven or unsupported - prices(provider, region, sku, unit, amount_minor, currency, effective_from, fetched_at) - estimates(run_id, resource_address, fixed_minor, usage_minor, usage_assumptions_json) - usage_profiles(id, name, assumptions_json, source) · source is declared or default, never inferred - diffs(run_id, base_run_id, resource_address, change_kind, delta_minor, coverage_changed) - An estimate stores fixed and usage-driven components separately, so a total is never reported without saying how much of the cost is not in it. ### Phase 1 · Resolving what the definitions actually create Build: Parse infrastructure definitions or a plan output into a resource graph with sizes, regions and counts resolved. Handle variables, modules and counts. Report a resource whose shape cannot be resolved rather than assuming a default. Trap: Assuming a default size for a resource whose shape depends on an unresolved variable produces an estimate for infrastructure nobody is deploying, and it looks identical to a correct one. Report unresolved shapes as unresolved and exclude them from the total rather than guessing. Done when: A real repository's definitions resolve to concrete resources with sizes and regions. Modules and counts are expanded correctly. An unresolved variable produces an unresolved resource rather than a defaulted one. The count of unresolved resources is reported alongside any total. Do not build yet: No pricing, coverage or diffs. ### Phase 2 · Prices with a date on them Build: Fetch and cache provider prices per region with the date fetched. Report the catalogue age with every estimate. Refuse to use a catalogue older than a stated threshold without an acknowledgement. Trap: A price catalogue cached indefinitely produces estimates that drift from reality without anything appearing to break, and the first sign is a bill that does not match a report from six months ago. Date the catalogue, show its age, and refuse to run silently on a stale one. Done when: Every price carries the date it was fetched and the catalogue age is shown with every estimate. A catalogue older than the threshold refuses to produce an estimate without an explicit acknowledgement. Region-specific pricing is used rather than a single default region. A price missing for a resource is reported rather than treated as zero. Do not build yet: No coverage, usage or diffs yet. ### Phase 3 · Saying what is not in the number Build: Classify each resource as priced, usage-driven or unsupported. Report the count and significance of each class alongside the total. Never omit an unsupported resource silently. Trap: Skipping resources the tool does not support, and reporting the total of the rest, produces a number that is confidently too low, and the resources most likely to be unsupported are often the expensive new ones. Count them, name them, and put the count where the total is. Done when: Every resource is classified as priced, usage-driven or unsupported and the counts appear beside the total. A resource with usage-driven costs is named as such rather than being priced at its fixed component alone. An unsupported resource is listed rather than omitted. Coverage is expressed as a proportion of resources, not of cost, since the cost of the excluded part is unknown. Do not build yet: No usage assumptions or diffs yet. ### Phase 4 · Usage assumptions, declared rather than guessed Build: Accept declared usage profiles for the resources that need them, from a file in the repository. Report the assumptions used beside the figures they produced. Never infer usage from resource shape. Trap: Inferring egress volume from an instance size produces a number with no basis that will nonetheless be quoted in a decision, and data transfer is precisely where estimates go most badly wrong. Require a declared profile, and where none exists report the resource as usage-driven with no figure rather than estimating one. Done when: Usage-driven costs appear only for resources with a declared profile. The assumptions used are printed beside every figure they produced. A resource with no declared usage is reported as usage-driven without a number rather than defaulted. Changing a usage profile changes the estimate and the change is attributable to the profile. Do not build yet: No diffs or integration yet. ### Phase 5 · The diff, where the decision happens Build: Compare a branch against its base and report the delta per resource in a pull request comment. Flag a change in coverage as well as in cost. Handle a first run with no baseline and a change that only moves resources between modules. Trap: Reporting a cost delta without reporting a coverage change lets a refactor that swaps a supported resource for an unsupported one appear as a cost reduction, when in fact the cost simply stopped being counted. Report coverage changes as prominently as cost changes. Done when: A branch reports its cost delta per resource against the base. A change that reduces coverage is flagged as prominently as a cost change, tested by replacing a supported resource with an unsupported one. A first run with no baseline reports the total rather than a delta. Moving a resource between modules does not appear as a deletion and an addition. Do not build yet: Nothing after this. ### Out of scope (and why) - Actual billing reconciliation, which requires the provider's own usage data and is a different product. - Provider and resource breadth, which is the hosted product's real value and an endless maintenance commitment. - Commitment and discount modelling, which depends on contracts the definitions know nothing about. - Any claim that an estimate predicts a bill, which it does not and cannot for usage-driven architectures. ### README must contain - That an estimate is a lower bound on a subset, since definitions cannot express usage - That every resource is classified as priced, usage-driven or unsupported and the counts appear beside the total - That usage costs require a declared profile and are never inferred from resource shape - That the price catalogue is dated and a stale one refuses to run silently - That a coverage reduction is flagged as prominently as a cost change
# Architecture · Infracost Cloud ## Stack | Part | Choice | Why | | --- | --- | --- | | Parsing | Infrastructure definitions parsed to a resource graph, provider-specific | the estimate is only as good as the resource shapes you resolve | | Pricing | A dated price catalogue per provider and region | prices change and a stale catalogue is a wrong estimate that looks fine | | Coverage | Explicit per-resource support state, with unsupported ones counted | silently skipping a resource understates the total | | Usage | Declared assumptions for usage-driven costs, never inferred | guessing egress volume produces a number nobody should trust | | Reporting | Diffs against a baseline, in a pull request comment | the useful moment is before the change is merged | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Parsing | Resolving definitions to concrete resources. | Any provider, provided unresolved shapes are reported rather than defaulted. | | Pricing | Dated regional catalogues. | Any source, provided the catalogue age is visible and staleness refuses. | | Coverage | Three-state classification reported with totals. | Not swappable for silent omission, which understates confidently. | | Usage | Declared profiles only. | Any format, provided nothing is inferred from resource shape. | | Diff | Per-resource deltas with coverage changes. | Any presentation, provided coverage changes are as prominent as cost. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `PRICE_CACHE_PATH` · required · Dated per fetch. A stale catalogue refuses to run silently. - `PRICE_MAX_AGE_DAYS` · required · Beyond this an estimate requires an explicit acknowledgement. - `USAGE_PROFILE_PATH` · required · Declared usage. Absent means usage-driven resources report no figure. - `INFER_USAGE` · required · Present as a constant. Inferred egress is a number with no basis. - `REPORT_COVERAGE` · required · Present as a constant. A total without coverage is not interpretable. - `DEFAULT_REGION` · required · Used only where a resource does not state one, and reported when it is. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Infracost Cloud product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Infrastructure definitions parsed to a resource graph, provider-specific, A dated price catalogue per provider and region, Explicit per-resource support state, with unsupported ones counted, Declared assumptions for usage-driven costs, never inferred, Diffs against a baseline, in a pull request comment. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Assuming a default size for a resource whose shape depends on an unresolved variable produces an estimate for infrastructure nobody is deploying, and it looks identical to a correct one. Report unresolved shapes as unresolved and exclude them from the total rather than guessing. - A price catalogue cached indefinitely produces estimates that drift from reality without anything appearing to break, and the first sign is a bill that does not match a report from six months ago. Date the catalogue, show its age, and refuse to run silently on a stale one. - Skipping resources the tool does not support, and reporting the total of the rest, produces a number that is confidently too low, and the resources most likely to be unsupported are often the expensive new ones. Count them, name them, and put the count where the total is. - Inferring egress volume from an instance size produces a number with no basis that will nonetheless be quoted in a decision, and data transfer is precisely where estimates go most badly wrong. Require a declared profile, and where none exists report the resource as usage-driven with no figure rather than estimating one. - Reporting a cost delta without reporting a coverage change lets a refactor that swaps a supported resource for an unsupported one appear as a cost reduction, when in fact the cost simply stopped being counted. Report coverage changes as prominently as cost changes.
# Delivery milestones · Infracost Cloud Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Resolving what the definitions actually create A plan resolves to concrete resources with their shapes. ### Steps 1. Resolve shapes, do not default them A defaulted size is an estimate for infrastructure nobody is deploying. Files: `src/parse.ts` 2. Expand modules and counts Where the resource count actually comes from. Files: `src/expand.ts` 3. Report unresolved resources Alongside the total, not in a log. Files: `src/report.ts` ### Done when - [ ] Definitions resolve to concrete resources - [ ] Modules and counts expand correctly - [ ] Unresolved variables produce unresolved resources - [ ] Unresolved counts appear with the total ### Watch out - Assuming a default size for a resource whose shape depends on an unresolved variable produces an estimate for infrastructure nobody is deploying, and it looks identical to a correct one. Report unresolved shapes as unresolved and exclude them from the total rather than guessing. ## M2 · Prices with a date on them Every price used is dated and its staleness is visible. ### Steps 1. Date the catalogue and show its age A stale catalogue is a wrong estimate that looks entirely normal. Files: `src/prices.ts` 2. Use region-specific prices The same resource differs substantially between regions. Files: `src/prices.ts` 3. Report missing prices as missing Treating an absent price as zero understates the total silently. Files: `src/estimate.ts` ### Done when - [ ] Prices carry fetch dates and the age is shown - [ ] A stale catalogue refuses without acknowledgement - [ ] Region-specific pricing is used - [ ] Missing prices are reported rather than zeroed ### Watch out - A price catalogue cached indefinitely produces estimates that drift from reality without anything appearing to break, and the first sign is a bill that does not match a report from six months ago. Date the catalogue, show its age, and refuse to run silently on a stale one. ## M3 · Saying what is not in the number Every estimate states its coverage explicitly. ### Steps 1. Classify every resource Priced, usage-driven, unsupported. Three states, all reported. Files: `src/coverage.ts` 2. Put coverage beside the total Not in a footnote. It is what makes the total interpretable. Files: `src/report.ts` 3. Express coverage by resource count By cost is impossible, since the excluded cost is what you do not know. Files: `src/coverage.ts` ### Done when - [ ] Every resource is classified into three states - [ ] Coverage appears beside the total - [ ] Usage-driven resources are named - [ ] Coverage is expressed by resource count ### Watch out - Skipping resources the tool does not support, and reporting the total of the rest, produces a number that is confidently too low, and the resources most likely to be unsupported are often the expensive new ones. Count them, name them, and put the count where the total is. ## M4 · Usage assumptions, declared rather than guessed Usage-driven costs appear only where someone stated the usage. ### Steps 1. Take usage from a declared file In the repository, reviewable alongside the change. Files: `src/usage.ts` 2. Print assumptions with the figures So a wrong number is traceable to a wrong assumption. Files: `src/report.ts` 3. Never infer usage from shape Egress from instance size is a number with no basis. Files: `tests/no-inference.test.ts` ### Done when - [ ] Usage costs require a declared profile - [ ] Assumptions print beside their figures - [ ] Undeclared usage produces no number - [ ] Profile changes are attributable ### Watch out - Inferring egress volume from an instance size produces a number with no basis that will nonetheless be quoted in a decision, and data transfer is precisely where estimates go most badly wrong. Require a declared profile, and where none exists report the resource as usage-driven with no figure rather than estimating one. ## M5 · The diff, where the decision happens (production only) A change shows its cost delta before it is merged. ### Steps 1. Diff per resource against the base The total delta hides the resource that changed. Files: `src/diff.ts` 2. Flag coverage changes prominently A refactor to an unsupported resource looks like a saving otherwise. Files: `src/diff.ts`, `tests/coverage-change.test.ts` 3. Match resources across moves A module rename should not read as deleting everything. Files: `src/matching.ts` ### Done when - [ ] Deltas are reported per resource - [ ] Coverage reductions are flagged prominently - [ ] A first run reports a total rather than a delta - [ ] Module moves do not read as delete and add ### Watch out - Reporting a cost delta without reporting a coverage change lets a refactor that swaps a supported resource for an unsupported one appear as a cost reduction, when in fact the cost simply stopped being counted. Report coverage changes as prominently as cost changes.
# Operations · Infracost Cloud ## Backup The price catalogue is regenerable and the estimates are derived. Usage profiles live in the repository with the code they describe, which is where they belong and where they are backed up already. ## Restore Restore and confirm the price catalogue is refreshed rather than restored stale, since an old catalogue produces estimates that look current and are not. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Catalogue age and the proportion of unsupported resources. The second grows quietly as teams adopt newer services, and a falling coverage figure is the leading indicator of estimates becoming useless. ## Incident checklist An approved change whose bill was many times the estimate is the failure that matters, and it is nearly always egress or another usage-driven cost the estimate correctly excluded but nobody read. That is why coverage sits beside the total rather than beneath it. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Unresolved variables are confirmed to produce unresolved resources - [ ] A stale catalogue is confirmed to refuse without acknowledgement - [ ] Coverage counts are confirmed to appear beside the total - [ ] Undeclared usage is confirmed to produce no figure - [ ] A coverage reduction is confirmed flagged prominently ## Launch constraint Do not market omitted Infracost Cloud capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Dated per fetch. A stale catalogue refuses to run silently. PRICE_CACHE_PATH=./cache/prices.json # Required. Beyond this an estimate requires an explicit acknowledgement. PRICE_MAX_AGE_DAYS=14 # Required. Declared usage. Absent means usage-driven resources report no figure. USAGE_PROFILE_PATH=./infracost-usage.yml # Required. Present as a constant. Inferred egress is a number with no basis. INFER_USAGE=never # Required. Present as a constant. A total without coverage is not interpretable. REPORT_COVERAGE=always # Required. Used only where a resource does not state one, and reported when it is. DEFAULT_REGION=eu-west-1
$ choose a build depth, inspect the files, then open the complete pack in your agent
Teams pay for maintained breadth and confidence rather than the Markdown comment itself. Infracost keeps a large multi-cloud pricing and resource model current, handles real-world IaC edge cases, centralizes policies and private pricing, integrates those checks across developer workflows, and gives FinOps teams organization-level visibility and governance without owning another internal platform.
xAWS, Azure, and Google Cloud coverage across the full Infracost resource catalog
xTerragrunt, CloudFormation, AWS CDK, modules, and complex Terraform expression coverage
xcustom price books, negotiated discounts, and organization-specific pricing
xIssue Explorer, campaigns, AutoFix, dashboards, audit trails, and team workflows
xenterprise identity, security, support, and broad CI/CD integrations
Infracost Cloud pricing
cloud$1000/mo · monthly · $12,000/yr
free tierInfracost CI/CD is free for up to 1,000 runs per month, but the Cloud plan adds policies, guardrails, dashboards, workflows, and audit trails.
verified 2026-08-09 · source ↗
Is Infracost Cloud free?
Infracost CI/CD is free for up to 1,000 runs per month, but the Cloud plan adds policies, guardrails, dashboards, workflows, and audit trails. Paid is Cloud at $1000/mo (checked 2026-08-09).
Vibecode Infracost Cloud
Kinda. The core of Infracost Cloud is buildable in a weekend with the prompt on this page, but there are real gaps: AWS, Azure, and Google Cloud coverage across the full Infracost resource catalog, Terragrunt, CloudFormation, AWS CDK, modules, and complex Terraform expression coverage. Read the honest list above before committing.
How much does Infracost Cloud cost?
Infracost Cloud costs about $1000/month (Cloud, checked 2026-08-09), which is $12000 per year.
What do I lose by replacing Infracost Cloud?
Honestly: AWS, Azure, and Google Cloud coverage across the full Infracost resource catalog; Terragrunt, CloudFormation, AWS CDK, modules, and complex Terraform expression coverage; custom price books, negotiated discounts, and organization-specific pricing; Issue Explorer, campaigns, AutoFix, dashboards, audit trails, and team workflows; enterprise identity, security, support, and broad CI/CD integrations. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Infracost Cloud?
Yes: Infracost (Apache-2.0 developer toolkit and the clearest reference for the product's resource-to-cost model and pull-request workflow.), AWS Price List (AWS-maintained bulk pricing data that a narrow replacement can consume without depending on Infracost's pricing API.). Using prior art is also vibecoding; the prompt is for when you want it exactly your way.