argus-cloud-optimizer 0.4.1__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/.env.example +31 -0
  2. {argus_cloud_optimizer-0.4.1/argus_cloud_optimizer.egg-info → argus_cloud_optimizer-0.6.0}/PKG-INFO +91 -13
  3. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/README.md +89 -12
  4. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/aws/cloudwatch.py +10 -3
  5. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/azure/cost_management.py +1 -1
  6. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/azure/monitor.py +10 -3
  7. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/base.py +5 -1
  8. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/gcp/cloud_monitoring.py +10 -1
  9. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0/argus_cloud_optimizer.egg-info}/PKG-INFO +91 -13
  10. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/argus_cloud_optimizer.egg-info/SOURCES.txt +21 -0
  11. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/argus_cloud_optimizer.egg-info/requires.txt +1 -0
  12. argus_cloud_optimizer-0.6.0/core/__version__.py +1 -0
  13. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/core/agent/chat.py +8 -4
  14. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/core/agent/loop.py +133 -1
  15. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/core/agent/prompts.py +36 -3
  16. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/core/config.py +16 -0
  17. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/core/models/finding.py +8 -0
  18. argus_cloud_optimizer-0.6.0/core/registry/__init__.py +52 -0
  19. argus_cloud_optimizer-0.6.0/core/registry/aws.py +578 -0
  20. argus_cloud_optimizer-0.6.0/core/registry/azure.py +699 -0
  21. argus_cloud_optimizer-0.6.0/core/registry/factory.py +49 -0
  22. argus_cloud_optimizer-0.6.0/core/registry/gcp.py +759 -0
  23. argus_cloud_optimizer-0.6.0/core/registry/models.py +26 -0
  24. argus_cloud_optimizer-0.6.0/core/registry/registry.py +62 -0
  25. argus_cloud_optimizer-0.6.0/core/remediation/__init__.py +376 -0
  26. argus_cloud_optimizer-0.6.0/core/remediation/audit.py +57 -0
  27. argus_cloud_optimizer-0.6.0/core/remediation/engine.py +244 -0
  28. argus_cloud_optimizer-0.6.0/core/remediation/loader.py +316 -0
  29. argus_cloud_optimizer-0.6.0/core/remediation/models.py +126 -0
  30. argus_cloud_optimizer-0.6.0/core/remediation/rightsizing.py +164 -0
  31. argus_cloud_optimizer-0.6.0/core/remediation/validator.py +196 -0
  32. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/core/reports/generator.py +32 -3
  33. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/core/reports/html.py +31 -4
  34. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/core/reports/multi_cloud.py +4 -2
  35. argus_cloud_optimizer-0.6.0/docs/blog/index.md +2 -0
  36. argus_cloud_optimizer-0.6.0/docs/blog/posts/react-vs-rule-based.md +113 -0
  37. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/concepts/adapters.md +15 -12
  38. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/concepts/ai-providers.md +3 -3
  39. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/concepts/ai-reasoning.md +11 -10
  40. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/concepts/how-it-works.md +28 -28
  41. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/contributing/development.md +25 -9
  42. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/contributing/index.md +2 -2
  43. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/contributing/new-adapter.md +37 -19
  44. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/contributing/new-ai-provider.md +12 -1
  45. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/deployment/aws.md +20 -11
  46. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/deployment/azure.md +17 -12
  47. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/deployment/gcp.md +15 -5
  48. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/deployment/multi-account.md +9 -11
  49. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/getting-started/configuration.md +15 -6
  50. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/getting-started/first-scan.md +7 -6
  51. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/getting-started/index.md +2 -2
  52. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/getting-started/quickstart.md +18 -9
  53. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/index.md +24 -27
  54. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/reference/env-vars.md +30 -7
  55. argus_cloud_optimizer-0.6.0/docs/reference/iam-permissions.md +173 -0
  56. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/reference/report-schema.md +15 -5
  57. argus_cloud_optimizer-0.6.0/docs/reference/resource-types.md +139 -0
  58. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/reference/security.md +19 -11
  59. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/reference/troubleshooting.md +24 -11
  60. argus_cloud_optimizer-0.6.0/docs/remediation/cli.md +215 -0
  61. argus_cloud_optimizer-0.6.0/docs/remediation/index.md +48 -0
  62. argus_cloud_optimizer-0.6.0/docs/remediation/jira.md +132 -0
  63. argus_cloud_optimizer-0.6.0/docs/remediation/policies.md +141 -0
  64. argus_cloud_optimizer-0.6.0/docs/roadmap.md +118 -0
  65. argus_cloud_optimizer-0.6.0/entrypoints/_remediation.py +159 -0
  66. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/entrypoints/aws_lambda.py +10 -1
  67. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/entrypoints/azure_function.py +10 -1
  68. argus_cloud_optimizer-0.6.0/entrypoints/cli.py +1154 -0
  69. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/entrypoints/gcp_cloudrun.py +10 -1
  70. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/pyproject.toml +1 -0
  71. argus_cloud_optimizer-0.4.1/core/__version__.py +0 -1
  72. argus_cloud_optimizer-0.4.1/docs/reference/iam-permissions.md +0 -145
  73. argus_cloud_optimizer-0.4.1/docs/reference/resource-types.md +0 -103
  74. argus_cloud_optimizer-0.4.1/docs/roadmap.md +0 -80
  75. argus_cloud_optimizer-0.4.1/entrypoints/cli.py +0 -387
  76. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/LICENSE +0 -0
  77. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/MANIFEST.in +0 -0
  78. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/accounts.yaml.example +0 -0
  79. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/__init__.py +0 -0
  80. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/aws/__init__.py +0 -0
  81. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/aws/adapter.py +0 -0
  82. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/aws/auth.py +0 -0
  83. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/aws/cloudtrail.py +0 -0
  84. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/aws/config.py +0 -0
  85. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/aws/cost_explorer.py +0 -0
  86. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/aws/resource_explorer.py +0 -0
  87. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/aws/retry.py +0 -0
  88. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/azure/__init__.py +0 -0
  89. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/azure/activity_log.py +0 -0
  90. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/azure/adapter.py +0 -0
  91. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/azure/resource_graph.py +0 -0
  92. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/azure/retry.py +0 -0
  93. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/gcp/__init__.py +0 -0
  94. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/gcp/adapter.py +0 -0
  95. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/gcp/asset_inventory.py +0 -0
  96. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/gcp/billing.py +0 -0
  97. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/gcp/cloud_logging.py +0 -0
  98. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/adapters/gcp/retry.py +0 -0
  99. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/ai/__init__.py +0 -0
  100. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/ai/anthropic.py +0 -0
  101. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/ai/azure_openai.py +0 -0
  102. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/ai/base.py +0 -0
  103. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/ai/bedrock.py +0 -0
  104. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/ai/vertexai.py +0 -0
  105. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/argus_cloud_optimizer.egg-info/dependency_links.txt +0 -0
  106. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/argus_cloud_optimizer.egg-info/entry_points.txt +0 -0
  107. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/argus_cloud_optimizer.egg-info/top_level.txt +0 -0
  108. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/core/__init__.py +0 -0
  109. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/core/agent/__init__.py +0 -0
  110. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/core/log.py +0 -0
  111. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/core/models/__init__.py +0 -0
  112. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/core/py.typed +0 -0
  113. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/core/reports/__init__.py +0 -0
  114. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/core/reports/comparison.py +0 -0
  115. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/core/reports/delivery.py +0 -0
  116. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/core/reports/export.py +0 -0
  117. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/core/secrets.py +0 -0
  118. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/core/token_tracker.py +0 -0
  119. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/core/validation.py +0 -0
  120. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/deploy/aws/multi-account/hub/template.yaml +0 -0
  121. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/deploy/aws/multi-account/spoke-role.yaml +0 -0
  122. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/deploy/aws/single-account/template.yaml +0 -0
  123. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/deploy/azure/function-app.bicep +0 -0
  124. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/deploy/gcp/deploy.sh +0 -0
  125. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/concepts/index.md +0 -0
  126. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/deployment/index.md +0 -0
  127. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/docs/reference/index.md +0 -0
  128. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/entrypoints/__init__.py +0 -0
  129. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/entrypoints/cli_chat.py +0 -0
  130. {argus_cloud_optimizer-0.4.1 → argus_cloud_optimizer-0.6.0}/setup.cfg +0 -0
@@ -68,6 +68,37 @@ REPORT_FORMAT=json,html # comma-separated: json, html, pdf,
68
68
  # Useful for local testing without a real webhook.
69
69
  DRY_RUN=false
70
70
 
71
+ # -----------------------------------------------------------------------------
72
+ # Remediation — Policy Engine + Jira Integration
73
+ # Policies are YAML files in ARGUS_POLICY_DIR. Argus never executes changes;
74
+ # it creates Jira tickets for human review. See config/policies/ for examples
75
+ # and CONTRIBUTING.md for how to write policies.
76
+ # -----------------------------------------------------------------------------
77
+ # Opt-in: create Jira tickets automatically after every scan (default: false).
78
+ # `argus policies apply --confirm` works without this; it is already explicit.
79
+ REMEDIATION_ENABLED=false
80
+
81
+ # Jira credentials — required for `argus policies apply --confirm`
82
+ # Create an API token at: https://id.atlassian.com/manage-profile/security/api-tokens
83
+ JIRA_BASE_URL=https://your-org.atlassian.net
84
+ JIRA_USER_EMAIL=bot@your-org.com
85
+ JIRA_API_TOKEN= # Atlassian API token (not your password)
86
+
87
+ # Directory containing your *.yaml policy files (one policy per file).
88
+ # Copy from config/policies.example/ to get started, then customise.
89
+ # Used by both `argus policies ...` and scheduled scans. The older name
90
+ # ARGUS_POLICIES_DIR is still accepted; if both are set, this one wins.
91
+ ARGUS_POLICY_DIR=./config/policies
92
+
93
+ # Path to integrations config (project key, labels, priority map).
94
+ # Copy config/integrations.yaml.example → config/integrations.yaml and fill in.
95
+ # config/integrations.yaml is gitignored — never commit credentials.
96
+ ARGUS_INTEGRATIONS_CONFIG=./config/integrations.yaml
97
+
98
+ # Audit log — every `apply --confirm` appends one JSONL line mapping
99
+ # proposal_id → jira_key → jira_url for traceability.
100
+ ARGUS_AUDIT_LOG=./local_reports/audit.jsonl
101
+
71
102
  # -----------------------------------------------------------------------------
72
103
  # Logging
73
104
  # -----------------------------------------------------------------------------
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: argus-cloud-optimizer
3
- Version: 0.4.1
3
+ Version: 0.6.0
4
4
  Summary: Argus — AI Cloud Detective for AWS, GCP, and Azure
5
5
  Author-email: Vamshi Siddarth Gaddam <vamshisiddarth02@gmail.com>
6
6
  License-Expression: MIT
@@ -59,6 +59,7 @@ Requires-Dist: types-python-dateutil>=2.9.0; extra == "dev"
59
59
  Requires-Dist: boto3-stubs[bedrock-runtime,ce,cloudwatch,resourceexplorer2,s3,sts]>=1.35.36; extra == "dev"
60
60
  Requires-Dist: mkdocs-material>=9.5.44; extra == "dev"
61
61
  Requires-Dist: mkdocs-minify-plugin>=0.8.0; extra == "dev"
62
+ Requires-Dist: mike>=2.1.0; extra == "dev"
62
63
  Dynamic: license-file
63
64
 
64
65
  <p align="center">
@@ -100,15 +101,26 @@ Top findings
100
101
 
101
102
  ---
102
103
 
103
- ## 🆕 What's new in v0.4.1
104
+ ## 🆕 What's new in v0.6.0
104
105
 
105
- > **Biggest release yet.** Argus jumps from scheduled reporter to a conversational FinOps companion.
106
+ > **Breaking:** scheduled scans now create Jira tickets only when `REMEDIATION_ENABLED=true` (default `false`). If you ran v0.5.0 with Jira configured, set it to keep tickets flowing. `argus policies apply --confirm` is unaffected.
106
107
 
107
- - **Interactive chat mode** — `argus chat` lets you ask natural-language questions about your cloud costs and get intelligent, context-aware answers backed by real metrics and cost data.
108
- - **True multi-cloud, multi-account** — GCP multi-project and Azure multi-subscription support with unified reporting across all accounts in a single run.
109
- - **Expanded resource coverage** — 31 GCP resource types (up from 22) and 40 Azure resource types (up from 26), all with curated metric mappings.
110
- - **Chat UX polish** — live spinner updates per tool call, AI-generated context summaries, turn-safe history trimming, and rich terminal formatting via `pip install argus-cloud-optimizer[chat]`.
111
- - **Docs overhaul** — new architecture diagram, corrected IAM permissions for all three clouds, and accurate env-var reference.
108
+ - **Dry runs never touch Jira** — `DRY_RUN=true` / `--dry-run` previously still created real tickets when Jira was configured.
109
+ - **One summary log line per remediation run**, with `error_type` on failures.
110
+ - **`ARGUS_POLICY_DIR`** is read by both the CLI and scheduled scans (`ARGUS_POLICIES_DIR` still accepted).
111
+
112
+ [Full changelog →](CHANGELOG.md) · [Project status →](docs/roadmap.md)
113
+
114
+ ### v0.5.0
115
+
116
+ > **Remediation is now first-class.** Argus goes beyond reporting — it generates Jira tickets, suggests exact resize targets, and tracks acceptance rates.
117
+
118
+ - **Policy engine** — YAML-driven policies match AI findings and generate prioritised `ChangeProposal` objects (weight-ordered, one proposal per resource per scan). Ships with 13 real-world policies across AWS, GCP, and Azure.
119
+ - **Jira integration** — idempotent ticket creation with ADF-structured descriptions (metrics table, runbook code block, snapshot fingerprint for diff-on-update). Dedup via deterministic label; diff-comment on re-scan.
120
+ - **Rightsizing recommendations** — `resize` and `reduce_nodes` proposals now include a specific target tier or node count derived from observed CPU% (e.g., "Recommend db.t3.small — observed CPU ~8%").
121
+ - **Acceptance rate monitoring** — `argus policies stats` reads the append-only audit log and prints per-policy proposal counts, Jira new/update splits, and cloud breakdown.
122
+ - **JSONL audit log** — every `apply --confirm` appends a line to `local_reports/audit.jsonl` mapping `proposal_id → jira_key → jira_url`.
123
+ - **1756 tests** — all pass offline, no cloud credentials needed.
112
124
 
113
125
  [Full changelog →](CHANGELOG.md)
114
126
 
@@ -129,6 +141,67 @@ The **Full report** button links to a self-contained HTML file (S3 / GCS / Azure
129
141
 
130
142
  ---
131
143
 
144
+ ## Remediation
145
+
146
+ Argus stays **strictly read-only** — it never mutates a resource. Instead, the policy engine turns AI findings into prioritised Jira tickets that a human reviews and acts on.
147
+
148
+ ### How it works
149
+
150
+ ```
151
+ argus scan → scan_report.json
152
+ │
153
+ ▼
154
+ argus policies plan --report scan_report.json
155
+ │
156
+ ├── evaluates policies in config/policies/ (13 bundled — weight-ordered, first-match-wins)
157
+ ├── applies two-tier conditions: cost/priority/idle-days + metric thresholds
158
+ ├── computes rightsizing target (e.g. "Recommend db.t3.small — CPU ~8%")
159
+ └── prints proposal table with total savings estimate
160
+ │
161
+ ▼ (add --confirm to create tickets)
162
+ argus policies apply --report scan_report.json --confirm
163
+ │
164
+ ├── creates Jira ticket per proposal (idempotent — dedup via label)
165
+ ├── diff-comments on re-scan if finding changed
166
+ ├── appends line to local_reports/audit.jsonl
167
+ └── prints per-ticket status
168
+
169
+ argus policies stats # acceptance rate per policy from audit log
170
+ ```
171
+
172
+ To create tickets automatically after every scheduled scan (Lambda, Cloud Run, Azure Function), set `REMEDIATION_ENABLED=true`. It is off by default; `apply --confirm` above works without it.
173
+
174
+ ### Bundled policies (13)
175
+
176
+ | Policy | Cloud | Action | Cost floor | Idle days |
177
+ |--------|-------|--------|-----------|-----------|
178
+ | `aws-redshift-snapshot-delete-14d` | AWS | snapshot_delete | $100/mo | 14 |
179
+ | `aws-rds-resize-high-cost-idle` | AWS | resize | $100/mo | — |
180
+ | `aws-ebs-delete-unattached-30d` | AWS | snapshot_delete | — | 30 |
181
+ | `aws-elasticache-delete-idle-30d` | AWS | delete | — | 30 |
182
+ | `aws-elb-delete-idle` | AWS | delete | $10/mo | — |
183
+ | `aws-ec2-stop-idle-14d` | AWS | stop | — | 14 |
184
+ | `aws-lambda-delete-unused-30d` | AWS | delete | — | 30 |
185
+ | `gcp-gke-reduce-nodes-underutilised` | GCP | reduce_nodes | $200/mo | — |
186
+ | `gcp-sql-stop-idle-14d` | GCP | stop | — | 14 |
187
+ | `gcp-compute-stop-idle-7d` | GCP | stop | — | 7 |
188
+ | `azure-aks-reduce-nodes-underutilised` | Azure | reduce_nodes | $150/mo | — |
189
+ | `azure-sql-resize-underutilised` | Azure | resize | $50/mo | — |
190
+ | `azure-vm-stop-idle-14d` | Azure | stop | — | 14 |
191
+
192
+ Each policy also has AI-priority and metric conditions; see [Policies](https://vamshisiddarth.github.io/argus/remediation/policies/#bundled-policies) for the full table.
193
+
194
+ All policies skip resources tagged `environment: prod` (or `production`) and resources tagged `argus-exempt: "true"` — either tag on its own is enough. Add your own in `config/policies/` — see [CONTRIBUTING.md](CONTRIBUTING.md).
195
+
196
+ ### Safety guarantees
197
+
198
+ - **IAM** — read-only roles; write APIs are unavailable even with creds attached
199
+ - **Code** — no cloud SDK write calls anywhere in the codebase
200
+ - **CLI gate** — `apply` is a dry-run by default; `--confirm` is required to create tickets
201
+ - **Human gate** — runbook is printed in the Jira ticket; Argus never executes it
202
+
203
+ ---
204
+
132
205
  ## Architecture
133
206
 
134
207
  ```
@@ -180,7 +253,7 @@ docker run --rm \
180
253
 
181
254
  ```bash
182
255
  pip install argus-cloud-optimizer
183
- argus --version # argus 0.4.1
256
+ argus --version # argus 0.6.0
184
257
  argus --help
185
258
  ```
186
259
 
@@ -245,7 +318,7 @@ argus chat # auto-detects cloud
245
318
  ```
246
319
 
247
320
  ```
248
- Argus v0.4.1 — AI Cloud Detective
321
+ Argus v0.6.0 — AI Cloud Detective
249
322
  Cloud: AWS | Accounts: prod (123456789012) | Budget: $1.00/session
250
323
  Tip: end a line with \ to continue on the next line.
251
324
  Type your question, or /help for commands.
@@ -545,7 +618,7 @@ Before you invest time deploying Argus, know what it **can't** do yet:
545
618
  | **AI non-determinism** | By design | The AI decides what's idle — different runs may produce slightly different findings or reasoning. Set `AI_TEMPERATURE=0.0` (default) for most consistent results. |
546
619
  | **LLM cost** | Configurable | A full scan of ~200 resources costs ~$0.05–$0.50 in LLM API fees depending on provider. Use `--llm-budget` to set a hard cap (default: $2.00/scan). Large estates (1000+ resources) will hit the budget limit — increase it or use `--max-resources`. |
547
620
  | **AWS Resource Explorer setup** | Manual step | You must enable Resource Explorer with an **aggregator index** (typically in `us-east-1`). Without this, Argus cannot discover resources. This is a one-time setup but is easy to miss. |
548
- | **Write actions** | None | Argus is read-only. It reports findings but never deletes, stops, or modifies resources. Remediation is manual. |
621
+ | **Write actions** | None (by design) | Argus is strictly read-only. The policy engine generates Jira tickets with runbooks — a human reviews and executes. Argus never calls the runbook itself. |
549
622
  | **Multi-cloud in one scan** | Not yet | Each `argus` invocation scans one cloud. Use the merge report feature (`core/reports/multi_cloud.py`) to combine results after separate runs. |
550
623
  | **Notifications** | Slack + Teams + webhook | No email. Slack/Teams delivery requires a webhook URL. |
551
624
 
@@ -587,6 +660,7 @@ argus/
587
660
  │ ├── agent/loop.py # ReAct agent loop
588
661
  │ ├── agent/prompts.py # System prompt + tool schemas
589
662
  │ ├── models/finding.py # ResourceFinding dataclass
663
+ │ ├── remediation/ # Policy engine — models, loader, validator, engine, audit, rightsizing
590
664
  │ └── reports/ # Report generator, multi-cloud merge, export, notifications
591
665
  ├── adapters/
592
666
  │ ├── base.py # CloudAdapter abstract class
@@ -599,8 +673,12 @@ argus/
599
673
  │ ├── bedrock.py # AWS Bedrock (Converse API)
600
674
  │ ├── vertexai.py # Vertex AI / Gemini (GCP)
601
675
  │ └── azure_openai.py # Azure OpenAI / GPT-4o (Azure)
676
+ ├── integrations/
677
+ │ └── jira/ # Ticket lifecycle: create, dedup, diff-comment, ADF formatter, webhook
678
+ ├── config/policies/ # 13 bundled YAML policies (AWS / GCP / Azure)
679
+ ├── config/policies.example/ # 3 annotated starter templates — copy here to begin
602
680
  ├── entrypoints/
603
- │ ├── cli.py # argus --cloud aws --run-now
681
+ │ ├── cli.py # argus scan / chat / policies validate|plan|apply|stats|docs
604
682
  │ ├── aws_lambda.py # AWS Lambda handler
605
683
  │ ├── gcp_cloudrun.py # GCP Cloud Run Job handler
606
684
  │ └── azure_function.py # Azure Function timer trigger
@@ -608,7 +686,7 @@ argus/
608
686
  │ ├── aws/ # CloudFormation templates
609
687
  │ ├── gcp/ # Cloud Run + Scheduler deploy script
610
688
  │ └── azure/ # Bicep templates
611
- └── tests/ # 560 tests, all pass offline
689
+ └── tests/ # 1756 tests, all pass offline
612
690
  ```
613
691
 
614
692
  ---
@@ -37,15 +37,26 @@ Top findings
37
37
 
38
38
  ---
39
39
 
40
- ## 🆕 What's new in v0.4.1
40
+ ## 🆕 What's new in v0.6.0
41
41
 
42
- > **Biggest release yet.** Argus jumps from scheduled reporter to a conversational FinOps companion.
42
+ > **Breaking:** scheduled scans now create Jira tickets only when `REMEDIATION_ENABLED=true` (default `false`). If you ran v0.5.0 with Jira configured, set it to keep tickets flowing. `argus policies apply --confirm` is unaffected.
43
43
 
44
- - **Interactive chat mode** — `argus chat` lets you ask natural-language questions about your cloud costs and get intelligent, context-aware answers backed by real metrics and cost data.
45
- - **True multi-cloud, multi-account** — GCP multi-project and Azure multi-subscription support with unified reporting across all accounts in a single run.
46
- - **Expanded resource coverage** — 31 GCP resource types (up from 22) and 40 Azure resource types (up from 26), all with curated metric mappings.
47
- - **Chat UX polish** — live spinner updates per tool call, AI-generated context summaries, turn-safe history trimming, and rich terminal formatting via `pip install argus-cloud-optimizer[chat]`.
48
- - **Docs overhaul** — new architecture diagram, corrected IAM permissions for all three clouds, and accurate env-var reference.
44
+ - **Dry runs never touch Jira** — `DRY_RUN=true` / `--dry-run` previously still created real tickets when Jira was configured.
45
+ - **One summary log line per remediation run**, with `error_type` on failures.
46
+ - **`ARGUS_POLICY_DIR`** is read by both the CLI and scheduled scans (`ARGUS_POLICIES_DIR` still accepted).
47
+
48
+ [Full changelog →](CHANGELOG.md) · [Project status →](docs/roadmap.md)
49
+
50
+ ### v0.5.0
51
+
52
+ > **Remediation is now first-class.** Argus goes beyond reporting — it generates Jira tickets, suggests exact resize targets, and tracks acceptance rates.
53
+
54
+ - **Policy engine** — YAML-driven policies match AI findings and generate prioritised `ChangeProposal` objects (weight-ordered, one proposal per resource per scan). Ships with 13 real-world policies across AWS, GCP, and Azure.
55
+ - **Jira integration** — idempotent ticket creation with ADF-structured descriptions (metrics table, runbook code block, snapshot fingerprint for diff-on-update). Dedup via deterministic label; diff-comment on re-scan.
56
+ - **Rightsizing recommendations** — `resize` and `reduce_nodes` proposals now include a specific target tier or node count derived from observed CPU% (e.g., "Recommend db.t3.small — observed CPU ~8%").
57
+ - **Acceptance rate monitoring** — `argus policies stats` reads the append-only audit log and prints per-policy proposal counts, Jira new/update splits, and cloud breakdown.
58
+ - **JSONL audit log** — every `apply --confirm` appends a line to `local_reports/audit.jsonl` mapping `proposal_id → jira_key → jira_url`.
59
+ - **1756 tests** — all pass offline, no cloud credentials needed.
49
60
 
50
61
  [Full changelog →](CHANGELOG.md)
51
62
 
@@ -66,6 +77,67 @@ The **Full report** button links to a self-contained HTML file (S3 / GCS / Azure
66
77
 
67
78
  ---
68
79
 
80
+ ## Remediation
81
+
82
+ Argus stays **strictly read-only** — it never mutates a resource. Instead, the policy engine turns AI findings into prioritised Jira tickets that a human reviews and acts on.
83
+
84
+ ### How it works
85
+
86
+ ```
87
+ argus scan → scan_report.json
88
+ │
89
+ ▼
90
+ argus policies plan --report scan_report.json
91
+ │
92
+ ├── evaluates policies in config/policies/ (13 bundled — weight-ordered, first-match-wins)
93
+ ├── applies two-tier conditions: cost/priority/idle-days + metric thresholds
94
+ ├── computes rightsizing target (e.g. "Recommend db.t3.small — CPU ~8%")
95
+ └── prints proposal table with total savings estimate
96
+ │
97
+ ▼ (add --confirm to create tickets)
98
+ argus policies apply --report scan_report.json --confirm
99
+ │
100
+ ├── creates Jira ticket per proposal (idempotent — dedup via label)
101
+ ├── diff-comments on re-scan if finding changed
102
+ ├── appends line to local_reports/audit.jsonl
103
+ └── prints per-ticket status
104
+
105
+ argus policies stats # acceptance rate per policy from audit log
106
+ ```
107
+
108
+ To create tickets automatically after every scheduled scan (Lambda, Cloud Run, Azure Function), set `REMEDIATION_ENABLED=true`. It is off by default; `apply --confirm` above works without it.
109
+
110
+ ### Bundled policies (13)
111
+
112
+ | Policy | Cloud | Action | Cost floor | Idle days |
113
+ |--------|-------|--------|-----------|-----------|
114
+ | `aws-redshift-snapshot-delete-14d` | AWS | snapshot_delete | $100/mo | 14 |
115
+ | `aws-rds-resize-high-cost-idle` | AWS | resize | $100/mo | — |
116
+ | `aws-ebs-delete-unattached-30d` | AWS | snapshot_delete | — | 30 |
117
+ | `aws-elasticache-delete-idle-30d` | AWS | delete | — | 30 |
118
+ | `aws-elb-delete-idle` | AWS | delete | $10/mo | — |
119
+ | `aws-ec2-stop-idle-14d` | AWS | stop | — | 14 |
120
+ | `aws-lambda-delete-unused-30d` | AWS | delete | — | 30 |
121
+ | `gcp-gke-reduce-nodes-underutilised` | GCP | reduce_nodes | $200/mo | — |
122
+ | `gcp-sql-stop-idle-14d` | GCP | stop | — | 14 |
123
+ | `gcp-compute-stop-idle-7d` | GCP | stop | — | 7 |
124
+ | `azure-aks-reduce-nodes-underutilised` | Azure | reduce_nodes | $150/mo | — |
125
+ | `azure-sql-resize-underutilised` | Azure | resize | $50/mo | — |
126
+ | `azure-vm-stop-idle-14d` | Azure | stop | — | 14 |
127
+
128
+ Each policy also has AI-priority and metric conditions; see [Policies](https://vamshisiddarth.github.io/argus/remediation/policies/#bundled-policies) for the full table.
129
+
130
+ All policies skip resources tagged `environment: prod` (or `production`) and resources tagged `argus-exempt: "true"` — either tag on its own is enough. Add your own in `config/policies/` — see [CONTRIBUTING.md](CONTRIBUTING.md).
131
+
132
+ ### Safety guarantees
133
+
134
+ - **IAM** — read-only roles; write APIs are unavailable even with creds attached
135
+ - **Code** — no cloud SDK write calls anywhere in the codebase
136
+ - **CLI gate** — `apply` is a dry-run by default; `--confirm` is required to create tickets
137
+ - **Human gate** — runbook is printed in the Jira ticket; Argus never executes it
138
+
139
+ ---
140
+
69
141
  ## Architecture
70
142
 
71
143
  ```
@@ -117,7 +189,7 @@ docker run --rm \
117
189
 
118
190
  ```bash
119
191
  pip install argus-cloud-optimizer
120
- argus --version # argus 0.4.1
192
+ argus --version # argus 0.6.0
121
193
  argus --help
122
194
  ```
123
195
 
@@ -182,7 +254,7 @@ argus chat # auto-detects cloud
182
254
  ```
183
255
 
184
256
  ```
185
- Argus v0.4.1 — AI Cloud Detective
257
+ Argus v0.6.0 — AI Cloud Detective
186
258
  Cloud: AWS | Accounts: prod (123456789012) | Budget: $1.00/session
187
259
  Tip: end a line with \ to continue on the next line.
188
260
  Type your question, or /help for commands.
@@ -482,7 +554,7 @@ Before you invest time deploying Argus, know what it **can't** do yet:
482
554
  | **AI non-determinism** | By design | The AI decides what's idle — different runs may produce slightly different findings or reasoning. Set `AI_TEMPERATURE=0.0` (default) for most consistent results. |
483
555
  | **LLM cost** | Configurable | A full scan of ~200 resources costs ~$0.05–$0.50 in LLM API fees depending on provider. Use `--llm-budget` to set a hard cap (default: $2.00/scan). Large estates (1000+ resources) will hit the budget limit — increase it or use `--max-resources`. |
484
556
  | **AWS Resource Explorer setup** | Manual step | You must enable Resource Explorer with an **aggregator index** (typically in `us-east-1`). Without this, Argus cannot discover resources. This is a one-time setup but is easy to miss. |
485
- | **Write actions** | None | Argus is read-only. It reports findings but never deletes, stops, or modifies resources. Remediation is manual. |
557
+ | **Write actions** | None (by design) | Argus is strictly read-only. The policy engine generates Jira tickets with runbooks — a human reviews and executes. Argus never calls the runbook itself. |
486
558
  | **Multi-cloud in one scan** | Not yet | Each `argus` invocation scans one cloud. Use the merge report feature (`core/reports/multi_cloud.py`) to combine results after separate runs. |
487
559
  | **Notifications** | Slack + Teams + webhook | No email. Slack/Teams delivery requires a webhook URL. |
488
560
 
@@ -524,6 +596,7 @@ argus/
524
596
  │ ├── agent/loop.py # ReAct agent loop
525
597
  │ ├── agent/prompts.py # System prompt + tool schemas
526
598
  │ ├── models/finding.py # ResourceFinding dataclass
599
+ │ ├── remediation/ # Policy engine — models, loader, validator, engine, audit, rightsizing
527
600
  │ └── reports/ # Report generator, multi-cloud merge, export, notifications
528
601
  ├── adapters/
529
602
  │ ├── base.py # CloudAdapter abstract class
@@ -536,8 +609,12 @@ argus/
536
609
  │ ├── bedrock.py # AWS Bedrock (Converse API)
537
610
  │ ├── vertexai.py # Vertex AI / Gemini (GCP)
538
611
  │ └── azure_openai.py # Azure OpenAI / GPT-4o (Azure)
612
+ ├── integrations/
613
+ │ └── jira/ # Ticket lifecycle: create, dedup, diff-comment, ADF formatter, webhook
614
+ ├── config/policies/ # 13 bundled YAML policies (AWS / GCP / Azure)
615
+ ├── config/policies.example/ # 3 annotated starter templates — copy here to begin
539
616
  ├── entrypoints/
540
- │ ├── cli.py # argus --cloud aws --run-now
617
+ │ ├── cli.py # argus scan / chat / policies validate|plan|apply|stats|docs
541
618
  │ ├── aws_lambda.py # AWS Lambda handler
542
619
  │ ├── gcp_cloudrun.py # GCP Cloud Run Job handler
543
620
  │ └── azure_function.py # Azure Function timer trigger
@@ -545,7 +622,7 @@ argus/
545
622
  │ ├── aws/ # CloudFormation templates
546
623
  │ ├── gcp/ # Cloud Run + Scheduler deploy script
547
624
  │ └── azure/ # Bicep templates
548
- └── tests/ # 560 tests, all pass offline
625
+ └── tests/ # 1756 tests, all pass offline
549
626
  ```
550
627
 
551
628
  ---
@@ -11,11 +11,18 @@ from botocore.exceptions import ClientError
11
11
  from adapters.aws.config import BOTO_TIMEOUT_CONFIG
12
12
  from adapters.aws.retry import retry_on_transient
13
13
  from adapters.base import MetricSummary
14
+ from core.registry import get_registry
14
15
 
15
16
  logger = structlog.get_logger(__name__)
16
17
 
17
- # (MetricName, Namespace, Stat, CloudWatch Dimension Key)
18
- # Stat is "Average" for utilisation metrics, "Sum" for throughput/count metrics.
18
+
19
+ def _metrics_for(resource_type: str) -> list[tuple[str, str, str, str]] | None:
20
+ spec = get_registry().get(resource_type)
21
+ if spec and spec.metrics:
22
+ return [(m.name, m.namespace, m.stat, m.dimension_key) for m in spec.metrics]
23
+ return None
24
+
25
+
19
26
  _METRICS: dict[str, list[tuple[str, str, str, str]]] = {
20
27
  "AWS::EC2::Instance": [
21
28
  ("CPUUtilization", "AWS/EC2", "Average", "InstanceId"),
@@ -282,7 +289,7 @@ def get_metrics(
282
289
  gives the AI the current instance size so it can recommend a specific
283
290
  smaller tier rather than a generic "consider downsizing".
284
291
  """
285
- metric_defs = _METRICS.get(resource_type)
292
+ metric_defs = _metrics_for(resource_type)
286
293
  if not metric_defs:
287
294
  metric_defs = _discover_metrics(session, resource_id, resource_type)
288
295
  if not metric_defs:
@@ -102,7 +102,7 @@ def _query_batch(
102
102
  aggregation={"totalCost": {"name": "PreTaxCost", "function": "Sum"}}, # type: ignore[dict-item]
103
103
  grouping=[QueryGrouping(type="Dimension", name="ResourceId")],
104
104
  filter=QueryFilter(
105
- dimensions=QueryComparisonExpression(
105
+ dimensions=QueryComparisonExpression( # type: ignore[call-overload]
106
106
  name="ResourceId",
107
107
  operator="In",
108
108
  values=resource_ids,
@@ -10,9 +10,18 @@ from azure.monitor.query import MetricAggregationType, MetricsQueryClient
10
10
 
11
11
  from adapters.azure.retry import retry_on_transient
12
12
  from adapters.base import MetricSummary
13
+ from core.registry import get_registry
13
14
 
14
15
  logger = structlog.get_logger(__name__)
15
16
 
17
+
18
+ def _metrics_for(resource_type: str) -> list[tuple[str, str]] | None:
19
+ spec = get_registry().get(resource_type.lower())
20
+ if spec and spec.metrics:
21
+ return [(m.name, m.stat) for m in spec.metrics]
22
+ return None
23
+
24
+
16
25
  # (MetricName, AggregationType)
17
26
  _METRICS: dict[str, list[tuple[str, str]]] = {
18
27
  # Virtual Machines
@@ -271,7 +280,7 @@ def get_metrics(
271
280
  Fetch Azure Monitor metrics for a resource.
272
281
  Falls back to querying available metric definitions for unknown resource types.
273
282
  """
274
- metric_defs = _METRICS.get(resource_type.lower())
283
+ metric_defs = _metrics_for(resource_type)
275
284
  if not metric_defs:
276
285
  metric_defs = _discover_metrics(resource_id, resource_type, credential)
277
286
  if not metric_defs:
@@ -363,8 +372,6 @@ def _discover_metrics(
363
372
  credential: Any = None,
364
373
  ) -> list[tuple[str, str]]:
365
374
  """Auto-discover available metrics for unknown Azure resource types."""
366
- from azure.monitor.query import MetricsQueryClient
367
-
368
375
  cred = credential or DefaultAzureCredential()
369
376
  client = MetricsQueryClient(cred, connection_timeout=10, read_timeout=60)
370
377
  discovered: list[tuple[str, str]] = []
@@ -52,7 +52,11 @@ class CloudAdapter(ABC):
52
52
  """
53
53
  Abstract cloud adapter. One implementation per cloud provider.
54
54
  The agent loop only ever calls these four methods — never raw SDK clients.
55
- All implementations must be read-only (no mutations to cloud resources).
55
+
56
+ READ-ONLY CONTRACT: All implementations must be strictly read-only.
57
+ No method may create, modify, delete, or mutate any cloud resource.
58
+ This contract is enforced by tests that scan adapter subclasses for
59
+ mutating method names. Violations fail CI.
56
60
  """
57
61
 
58
62
  @abstractmethod
@@ -10,9 +10,18 @@ from google.protobuf.timestamp_pb2 import Timestamp
10
10
 
11
11
  from adapters.base import MetricSummary
12
12
  from adapters.gcp.retry import retry_on_transient
13
+ from core.registry import get_registry
13
14
 
14
15
  logger = structlog.get_logger(__name__)
15
16
 
17
+
18
+ def _metrics_for(resource_type: str) -> list[tuple[str, str]] | None:
19
+ spec = get_registry().get(resource_type)
20
+ if spec and spec.metrics:
21
+ return [(m.name, m.stat) for m in spec.metrics]
22
+ return None
23
+
24
+
16
25
  # (MetricType, Stat, label_key_for_resource_filter)
17
26
  # Stat: "mean" for utilisation, "sum" for throughput/count.
18
27
  # label_key: the monitored-resource label used to filter to this specific resource.
@@ -254,7 +263,7 @@ def get_metrics(
254
263
  Fetch Cloud Monitoring metrics for a GCP resource.
255
264
  Falls back to listing available metrics for unknown resource types.
256
265
  """
257
- metric_defs = _METRICS.get(resource_type)
266
+ metric_defs = _metrics_for(resource_type)
258
267
  if not metric_defs:
259
268
  metric_defs = _discover_metrics(project_id, resource_id, resource_type)
260
269
  if not metric_defs: