sanityops-cli 0.1.3__tar.gz → 0.1.4__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 (120) hide show
  1. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/PKG-INFO +85 -1
  2. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/README.md +84 -0
  3. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/pyproject.toml +1 -1
  4. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/__init__.py +1 -1
  5. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/commands/config.py +6 -3
  6. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/utils/config_resolver.py +15 -11
  7. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/utils/test_config_resolver.py +25 -7
  8. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  9. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  10. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  11. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  12. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/.github/dependabot.yml +0 -0
  13. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/.github/scripts/pr_review_bot.py +0 -0
  14. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/.github/workflows/ai-review.yml +0 -0
  15. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/.github/workflows/ci.yml +0 -0
  16. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/.github/workflows/release.yml +0 -0
  17. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/.gitignore +0 -0
  18. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/CHANGELOG.md +0 -0
  19. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/CODE_OF_CONDUCT.md +0 -0
  20. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/CONTRIBUTING.md +0 -0
  21. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/LICENSE +0 -0
  22. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/NOTICE +0 -0
  23. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/SECURITY.md +0 -0
  24. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/docs/superpowers/plans/2026-08-13-model-config-comments-in-init-template.md +0 -0
  25. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/docs/superpowers/plans/2026-08-14-loading-animations.md +0 -0
  26. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/docs/superpowers/plans/2026-08-14-skills-files-banner-readme.md +0 -0
  27. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/docs/superpowers/plans/2026-08-28-copyright-compliance.md +0 -0
  28. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/docs/superpowers/plans/2026-09-08-config-command.md +0 -0
  29. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/docs/superpowers/plans/2026-09-20-cross-artifact-rendering.md +0 -0
  30. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/docs/superpowers/plans/2026-09-20-help-panel-migration.md +0 -0
  31. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/docs/superpowers/plans/2026-09-20-repair-feature.md +0 -0
  32. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/docs/superpowers/specs/2026-08-13-model-config-comments-in-init-template-design.md +0 -0
  33. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/docs/superpowers/specs/2026-08-14-loading-animations-design.md +0 -0
  34. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/docs/superpowers/specs/2026-08-14-skills-files-banner-readme-design.md +0 -0
  35. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/docs/superpowers/specs/2026-08-28-copyright-compliance-design.md +0 -0
  36. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/docs/superpowers/specs/2026-09-08-config-command-design.md +0 -0
  37. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/docs/superpowers/specs/2026-09-20-cross-artifact-rendering-design.md +0 -0
  38. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/docs/superpowers/specs/2026-09-20-help-panel-migration-design.md +0 -0
  39. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/docs/superpowers/specs/2026-09-20-repair-feature-design.md +0 -0
  40. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/entry.py +0 -0
  41. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/release.sh +0 -0
  42. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/scripts/generate-checksums.ps1 +0 -0
  43. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/scripts/install.ps1 +0 -0
  44. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/scripts/install.sh +0 -0
  45. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/agents/__init__.py +0 -0
  46. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/agents/repair_agent/__init__.py +0 -0
  47. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/agents/repair_agent/agent.py +0 -0
  48. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/agents/repair_agent/prompts.py +0 -0
  49. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/agents/repair_agent/tools/__init__.py +0 -0
  50. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/agents/repair_agent/tools/store_repairs_tool.py +0 -0
  51. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/agents/scanner_agent/agent.py +0 -0
  52. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/agents/scanner_agent/hooks/progress_hook.py +0 -0
  53. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/agents/scanner_agent/models/finding.py +0 -0
  54. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/agents/scanner_agent/prompts.py +0 -0
  55. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/agents/scanner_agent/tools/grep_tool.py +0 -0
  56. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/agents/scanner_agent/tools/listfiles_tool.py +0 -0
  57. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/agents/scanner_agent/tools/readfile_tool.py +0 -0
  58. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/agents/scanner_agent/tools/storefindings_tool.py +0 -0
  59. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/api/__init__.py +0 -0
  60. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/api/client.py +0 -0
  61. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/commands/__init__.py +0 -0
  62. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/commands/init.py +0 -0
  63. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/commands/inspect.py +0 -0
  64. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/constants/__init__.py +0 -0
  65. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/constants/config_defaults.py +0 -0
  66. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/constants/exit_codes.py +0 -0
  67. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/defect_checker/__init__.py +0 -0
  68. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/defect_checker/checker.py +0 -0
  69. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/defect_checker/llm_config.py +0 -0
  70. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/defect_checker/markdown_reporter.py +0 -0
  71. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/defect_checker/renderer.py +0 -0
  72. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/exceptions/__init__.py +0 -0
  73. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/exceptions/api_exceptions.py +0 -0
  74. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/exceptions/base_exceptions.py +0 -0
  75. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/help_panel.py +0 -0
  76. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/logging/__init__.py +0 -0
  77. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/logging/logger.py +0 -0
  78. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/main.py +0 -0
  79. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/progress/__init__.py +0 -0
  80. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/progress/tracker.py +0 -0
  81. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/renderers/__init__.py +0 -0
  82. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/renderers/command_renderer/inspect_command_renderer.py +0 -0
  83. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/templates/__init__.py +0 -0
  84. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/templates/inspect_config.yaml +0 -0
  85. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/utils/__init__.py +0 -0
  86. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/utils/artifact_packer.py +0 -0
  87. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/utils/config_loader.py +0 -0
  88. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/src/sanityops_cli/utils/validators.py +0 -0
  89. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/__init__.py +0 -0
  90. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/commands/__init__.py +0 -0
  91. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/commands/test_config.py +0 -0
  92. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/commands/test_init.py +0 -0
  93. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/commands/test_inspect_repair.py +0 -0
  94. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/test_help_panel.py +0 -0
  95. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/__init__.py +0 -0
  96. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/agents/__init__.py +0 -0
  97. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/agents/repair_agent/__init__.py +0 -0
  98. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/agents/repair_agent/test_agent.py +0 -0
  99. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/agents/repair_agent/test_store_repairs_tool.py +0 -0
  100. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/agents/scanner_agent/__init__.py +0 -0
  101. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/agents/scanner_agent/models/__init__.py +0 -0
  102. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/agents/scanner_agent/models/test_finding.py +0 -0
  103. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/agents/scanner_agent/test_agent.py +0 -0
  104. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/agents/scanner_agent/test_progress_hook.py +0 -0
  105. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/agents/scanner_agent/test_prompts.py +0 -0
  106. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/api/__init__.py +0 -0
  107. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/api/test_client.py +0 -0
  108. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/commands/test_inspect.py +0 -0
  109. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/commands/test_version.py +0 -0
  110. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/defect_checker/__init__.py +0 -0
  111. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/defect_checker/test_checker.py +0 -0
  112. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/defect_checker/test_llm_config.py +0 -0
  113. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/defect_checker/test_markdown_reporter.py +0 -0
  114. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/defect_checker/test_renderer.py +0 -0
  115. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/logging/__init__.py +0 -0
  116. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/logging/test_logger.py +0 -0
  117. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/progress/__init__.py +0 -0
  118. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/progress/test_tracker.py +0 -0
  119. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/utils/test_artifact_packer.py +0 -0
  120. {sanityops_cli-0.1.3 → sanityops_cli-0.1.4}/tests/unit/utils/test_config_loader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sanityops-cli
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: CLI tool for static defect inspection of AI logical artifacts
5
5
  Project-URL: Homepage, https://sanityops.org
6
6
  Project-URL: Documentation, https://sanityops.org
@@ -147,6 +147,90 @@ sanityops-cli inspect --skip-defect-check
147
147
  sanityops-cli inspect --config path/to/config.yaml
148
148
  ```
149
149
 
150
+ ### 4. Generate Repairs (Optional)
151
+
152
+ After inspection, generate repaired artifacts from the report:
153
+
154
+ ```bash
155
+ # Use the latest inspection report
156
+ sanityops-cli inspect repair
157
+
158
+ # Use a specific report
159
+ sanityops-cli inspect repair --report .sanityops/results/inspect-20250115-120000.md
160
+
161
+ # Specify timeout and token budget for large projects
162
+ sanityops-cli inspect repair --timeout 3600 --token-budget 500000
163
+ ```
164
+
165
+ Repairs are written to `.sanityops/repairs/repair-<timestamp>.md` without modifying source files.
166
+
167
+ ## Configuration
168
+
169
+ ### Config Command
170
+
171
+ Manage server-side configuration (base URL, API key) using the `config` command:
172
+
173
+ ```bash
174
+ # Read a config value
175
+ sanityops-cli config server.base_url
176
+
177
+ # Set a global config value
178
+ sanityops-cli config server.base_url https://api.sanityops.org
179
+
180
+ # Set a project-level config value
181
+ sanityops-cli config server.base_url https://api.sanityops.org --local
182
+
183
+ # Set API key (prompts for masked input if no value provided)
184
+ sanityops-cli config server.api_key
185
+
186
+ # List all config values
187
+ sanityops-cli config --list
188
+
189
+ # Remove a config key
190
+ sanityops-cli config --unset server.base_url
191
+ ```
192
+
193
+ ### Configuration Precedence
194
+
195
+ Values are resolved in this priority order (highest to lowest):
196
+
197
+ 1. **Environment Variable** — e.g., `SANITYOPS_BASE_URL`
198
+ 2. **Project-level** — `.sanityops/inspect_config.yaml`
199
+ 3. **Global** — `~/.sanityops/config`
200
+ 4. **Default** — built-in fallback
201
+
202
+ This ordering ensures CI/CD pipelines can override any project config by injecting environment variables.
203
+
204
+ ### Environment Variables
205
+
206
+ | Variable | Description |
207
+ |----------|-------------|
208
+ | `SANITYOPS_BASE_URL` | Override `server.base_url` |
209
+ | `SANITYOPS_API_KEY` | Override `server.api_key` |
210
+ | `LLM_PROVIDER` | LLM provider (e.g., `anthropic`, `openai`) |
211
+ | `LLM_API_KEY` | LLM API key for defect checking |
212
+ | `LLM_MODEL_ID` | LLM model ID (e.g., `claude-sonnet-4-20250514`) |
213
+ | `LLM_BASE_URL` | Custom LLM base URL |
214
+
215
+ ## Advanced Configuration
216
+
217
+ ### Agent Settings
218
+
219
+ You can tune the scanner agent behavior in `.sanityops/inspect_config.yaml`:
220
+
221
+ ```yaml
222
+ agent:
223
+ max_loops: 60 # Maximum agent execution loops
224
+ token_budget: 200000 # Token budget for agent execution
225
+ timeout: 1800 # Timeout in seconds (default: 1800 = 30min)
226
+ ```
227
+
228
+ CLI flags (`--timeout`, `--token-budget`) override these values when explicitly set.
229
+
230
+ ### Logging
231
+
232
+ All CLI operations are logged to `~/.sanityops/logs/sanityops-cli-<YYYY-MM-DD>.log`. Logs include timestamps, log levels, and redacted API keys for security.
233
+
150
234
  ### Check Levels
151
235
 
152
236
  | Level | Description | Use Case |
@@ -107,6 +107,90 @@ sanityops-cli inspect --skip-defect-check
107
107
  sanityops-cli inspect --config path/to/config.yaml
108
108
  ```
109
109
 
110
+ ### 4. Generate Repairs (Optional)
111
+
112
+ After inspection, generate repaired artifacts from the report:
113
+
114
+ ```bash
115
+ # Use the latest inspection report
116
+ sanityops-cli inspect repair
117
+
118
+ # Use a specific report
119
+ sanityops-cli inspect repair --report .sanityops/results/inspect-20250115-120000.md
120
+
121
+ # Specify timeout and token budget for large projects
122
+ sanityops-cli inspect repair --timeout 3600 --token-budget 500000
123
+ ```
124
+
125
+ Repairs are written to `.sanityops/repairs/repair-<timestamp>.md` without modifying source files.
126
+
127
+ ## Configuration
128
+
129
+ ### Config Command
130
+
131
+ Manage server-side configuration (base URL, API key) using the `config` command:
132
+
133
+ ```bash
134
+ # Read a config value
135
+ sanityops-cli config server.base_url
136
+
137
+ # Set a global config value
138
+ sanityops-cli config server.base_url https://api.sanityops.org
139
+
140
+ # Set a project-level config value
141
+ sanityops-cli config server.base_url https://api.sanityops.org --local
142
+
143
+ # Set API key (prompts for masked input if no value provided)
144
+ sanityops-cli config server.api_key
145
+
146
+ # List all config values
147
+ sanityops-cli config --list
148
+
149
+ # Remove a config key
150
+ sanityops-cli config --unset server.base_url
151
+ ```
152
+
153
+ ### Configuration Precedence
154
+
155
+ Values are resolved in this priority order (highest to lowest):
156
+
157
+ 1. **Environment Variable** — e.g., `SANITYOPS_BASE_URL`
158
+ 2. **Project-level** — `.sanityops/inspect_config.yaml`
159
+ 3. **Global** — `~/.sanityops/config`
160
+ 4. **Default** — built-in fallback
161
+
162
+ This ordering ensures CI/CD pipelines can override any project config by injecting environment variables.
163
+
164
+ ### Environment Variables
165
+
166
+ | Variable | Description |
167
+ |----------|-------------|
168
+ | `SANITYOPS_BASE_URL` | Override `server.base_url` |
169
+ | `SANITYOPS_API_KEY` | Override `server.api_key` |
170
+ | `LLM_PROVIDER` | LLM provider (e.g., `anthropic`, `openai`) |
171
+ | `LLM_API_KEY` | LLM API key for defect checking |
172
+ | `LLM_MODEL_ID` | LLM model ID (e.g., `claude-sonnet-4-20250514`) |
173
+ | `LLM_BASE_URL` | Custom LLM base URL |
174
+
175
+ ## Advanced Configuration
176
+
177
+ ### Agent Settings
178
+
179
+ You can tune the scanner agent behavior in `.sanityops/inspect_config.yaml`:
180
+
181
+ ```yaml
182
+ agent:
183
+ max_loops: 60 # Maximum agent execution loops
184
+ token_budget: 200000 # Token budget for agent execution
185
+ timeout: 1800 # Timeout in seconds (default: 1800 = 30min)
186
+ ```
187
+
188
+ CLI flags (`--timeout`, `--token-budget`) override these values when explicitly set.
189
+
190
+ ### Logging
191
+
192
+ All CLI operations are logged to `~/.sanityops/logs/sanityops-cli-<YYYY-MM-DD>.log`. Logs include timestamps, log levels, and redacted API keys for security.
193
+
110
194
  ### Check Levels
111
195
 
112
196
  | Level | Description | Use Case |
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sanityops-cli"
7
- version = "0.1.3"
7
+ version = "0.1.4"
8
8
  description = "CLI tool for static defect inspection of AI logical artifacts"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -13,4 +13,4 @@
13
13
  # limitations under the License.
14
14
  #
15
15
 
16
- __version__ = "0.1.3"
16
+ __version__ = "0.1.4"
@@ -17,7 +17,7 @@
17
17
 
18
18
  Config keys use dot notation, e.g. ``server.base_url``. They correspond to
19
19
  the sanityops SaaS backend (NOT the LLM model config, which lives under
20
- ``model.*``). Precedence: project-level > global > env var > default.
20
+ ``model.*``). Precedence: environment variable > project-level > global > default.
21
21
  """
22
22
 
23
23
  from __future__ import annotations
@@ -110,8 +110,11 @@ def _get_config_usage_panel():
110
110
 
111
111
  usage_text = f"""\
112
112
  Configuration Priority:
113
- Project-level (.sanityops/inspect_config.yaml) > Global (~/.sanityops/config)
114
- > Environment Variables > Default
113
+ Environment Variable > Project-level (.sanityops/inspect_config.yaml)
114
+ > Global (~/.sanityops/config) > Default
115
+
116
+ This ordering ensures CI/CD pipelines can override project config by
117
+ injecting environment variables (e.g., SANITYOPS_BASE_URL).
115
118
 
116
119
  Available Keys:
117
120
  server.base_url Sanityops SaaS backend URL (default: {DEFAULT_SERVER_BASE_URL})
@@ -13,7 +13,7 @@
13
13
  # limitations under the License.
14
14
  #
15
15
 
16
- """Generic config resolver with precedence chain: project > global > env > default."""
16
+ """Generic config resolver with precedence chain: env > project > global > default."""
17
17
 
18
18
  from __future__ import annotations
19
19
 
@@ -214,7 +214,11 @@ def _unset_nested_value(data: dict[str, Any], key: str) -> bool:
214
214
  class ConfigResolver:
215
215
  """Generic config resolver with precedence chain.
216
216
 
217
- Precedence: project-level > global > environment variable > default.
217
+ Precedence: environment variable > project-level > global > default.
218
+
219
+ This ordering ensures CI/CD pipelines can override project config by
220
+ injecting environment variables, while local development uses project-level
221
+ files as the primary source.
218
222
 
219
223
  Attributes:
220
224
  config_key: Dot-separated config key (e.g., "server.base_url").
@@ -238,25 +242,25 @@ class ConfigResolver:
238
242
  Returns:
239
243
  The resolved value, or None if not found and no default.
240
244
  """
241
- # 1. Project-level config
245
+ # 1. Environment variable (highest priority for CI/CD overrides)
246
+ if self.env_var:
247
+ env_value = os.environ.get(self.env_var)
248
+ if env_value is not None:
249
+ return env_value
250
+
251
+ # 2. Project-level config
242
252
  project_config = _read_yaml_file(get_project_config_path())
243
253
  value = _get_nested_value(project_config, self.config_key)
244
254
  if value is not None:
245
255
  return str(value) if not isinstance(value, str) else value
246
256
 
247
- # 2. Global config
257
+ # 3. Global config
248
258
  global_config = _read_yaml_file(get_global_config_path())
249
259
  value = _get_nested_value(global_config, self.config_key)
250
260
  if value is not None:
251
261
  return str(value) if not isinstance(value, str) else value
252
262
 
253
- # 3. Environment variable
254
- if self.env_var:
255
- env_value = os.environ.get(self.env_var)
256
- if env_value is not None:
257
- return env_value
258
-
259
- # 4. Default
263
+ # 4. Default (lowest priority)
260
264
  return self.default
261
265
 
262
266
  def resolve_all_sources(self) -> dict[str, str | None]:
@@ -232,7 +232,7 @@ class TestNestedKeyOperations:
232
232
 
233
233
 
234
234
  class TestConfigResolverPrecedence:
235
- """Test precedence chain: project > global > env > default."""
235
+ """Test precedence chain: env > project > global > default."""
236
236
 
237
237
  def test_resolve_returns_default_when_nothing_set(self, monkeypatch, tmp_path: Path):
238
238
  """Should return default when no config or env set."""
@@ -258,8 +258,8 @@ class TestConfigResolverPrecedence:
258
258
  result = resolver.resolve()
259
259
  assert result == "https://env.example.com"
260
260
 
261
- def test_resolve_global_overrides_env(self, monkeypatch, tmp_path: Path):
262
- """Global config should override env and default."""
261
+ def test_resolve_env_overrides_global(self, monkeypatch, tmp_path: Path):
262
+ """Environment variable should override global config and default."""
263
263
  from sanityops_cli.utils.config_resolver import ConfigResolver
264
264
 
265
265
  monkeypatch.chdir(tmp_path)
@@ -275,16 +275,15 @@ class TestConfigResolverPrecedence:
275
275
 
276
276
  resolver = ConfigResolver("server.base_url", env_var="SANITYOPS_BASE_URL", default="https://default.com")
277
277
  result = resolver.resolve()
278
- assert result == "https://global.example.com"
278
+ assert result == "https://env.example.com"
279
279
 
280
280
  def test_resolve_project_overrides_global(self, monkeypatch, tmp_path: Path):
281
- """Project config should override global, env, default."""
281
+ """Project config should override global and default."""
282
282
  from sanityops_cli.utils.config_resolver import ConfigResolver
283
283
 
284
284
  monkeypatch.chdir(tmp_path)
285
285
  home_dir = tmp_path / "home"
286
286
  monkeypatch.setattr(Path, "home", lambda: home_dir)
287
- monkeypatch.setenv("SANITYOPS_BASE_URL", "https://env.example.com")
288
287
 
289
288
  # Create global config
290
289
  global_config_dir = home_dir / ".sanityops"
@@ -302,8 +301,27 @@ class TestConfigResolverPrecedence:
302
301
  result = resolver.resolve()
303
302
  assert result == "https://project.example.com"
304
303
 
304
+ def test_resolve_env_overrides_project(self, monkeypatch, tmp_path: Path):
305
+ """Environment variable should override project config."""
306
+ from sanityops_cli.utils.config_resolver import ConfigResolver
307
+
308
+ monkeypatch.chdir(tmp_path)
309
+ home_dir = tmp_path / "home"
310
+ monkeypatch.setattr(Path, "home", lambda: home_dir)
311
+ monkeypatch.setenv("SANITYOPS_BASE_URL", "https://env.example.com")
312
+
313
+ # Create project config
314
+ project_config_dir = tmp_path / ".sanityops"
315
+ project_config_dir.mkdir(parents=True)
316
+ project_config = project_config_dir / "inspect_config.yaml"
317
+ project_config.write_text("server:\n base_url: https://project.example.com\n")
318
+
319
+ resolver = ConfigResolver("server.base_url", env_var="SANITYOPS_BASE_URL", default="https://default.com")
320
+ result = resolver.resolve()
321
+ assert result == "https://env.example.com"
322
+
305
323
  def test_resolve_all_sources_returns_all_values(self, monkeypatch, tmp_path: Path):
306
- """Should return values from project, global, env, and default sources."""
324
+ """Should return values from env, project, global, and default sources."""
307
325
  from sanityops_cli.utils.config_resolver import ConfigResolver
308
326
 
309
327
  monkeypatch.chdir(tmp_path)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes