specsmith 0.2.2.dev50__tar.gz → 0.2.3.dev1__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 (105) hide show
  1. {specsmith-0.2.2.dev50/src/specsmith.egg-info → specsmith-0.2.3.dev1}/PKG-INFO +37 -4
  2. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/README.md +36 -3
  3. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/pyproject.toml +1 -1
  4. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/__init__.py +1 -1
  5. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/cli.py +162 -0
  6. specsmith-0.2.3.dev1/src/specsmith/rate_limits.py +784 -0
  7. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/governance/rules.md.j2 +12 -0
  8. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/validator.py +93 -0
  9. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1/src/specsmith.egg-info}/PKG-INFO +37 -4
  10. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith.egg-info/SOURCES.txt +2 -0
  11. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/tests/test_cli.py +38 -0
  12. specsmith-0.2.3.dev1/tests/test_rate_limits.py +276 -0
  13. specsmith-0.2.3.dev1/tests/test_validator.py +142 -0
  14. specsmith-0.2.2.dev50/tests/test_validator.py +0 -62
  15. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/LICENSE +0 -0
  16. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/setup.cfg +0 -0
  17. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/__main__.py +0 -0
  18. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/architect.py +0 -0
  19. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/auditor.py +0 -0
  20. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/commands/__init__.py +0 -0
  21. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/compressor.py +0 -0
  22. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/config.py +0 -0
  23. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/credit_analyzer.py +0 -0
  24. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/credits.py +0 -0
  25. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/differ.py +0 -0
  26. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/doctor.py +0 -0
  27. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/executor.py +0 -0
  28. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/exporter.py +0 -0
  29. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/importer.py +0 -0
  30. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/integrations/__init__.py +0 -0
  31. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/integrations/aider.py +0 -0
  32. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/integrations/base.py +0 -0
  33. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/integrations/claude_code.py +0 -0
  34. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/integrations/copilot.py +0 -0
  35. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/integrations/cursor.py +0 -0
  36. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/integrations/gemini.py +0 -0
  37. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/integrations/warp.py +0 -0
  38. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/integrations/windsurf.py +0 -0
  39. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/ledger.py +0 -0
  40. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/plugins.py +0 -0
  41. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/releaser.py +0 -0
  42. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/requirements.py +0 -0
  43. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/scaffolder.py +0 -0
  44. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/session.py +0 -0
  45. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/agents.md.j2 +0 -0
  46. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/community/bug_report.md.j2 +0 -0
  47. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/community/code_of_conduct.md.j2 +0 -0
  48. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/community/contributing.md.j2 +0 -0
  49. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/community/feature_request.md.j2 +0 -0
  50. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/community/license-Apache-2.0.j2 +0 -0
  51. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/community/license-MIT.j2 +0 -0
  52. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/community/pull_request_template.md.j2 +0 -0
  53. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/community/security.md.j2 +0 -0
  54. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/docs/architecture.md.j2 +0 -0
  55. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/docs/mkdocs.yml.j2 +0 -0
  56. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/docs/readthedocs.yaml.j2 +0 -0
  57. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/docs/requirements.md.j2 +0 -0
  58. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/docs/test-spec.md.j2 +0 -0
  59. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/docs/workflow.md.j2 +0 -0
  60. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/editorconfig.j2 +0 -0
  61. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/gitattributes.j2 +0 -0
  62. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/gitignore.j2 +0 -0
  63. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/go/go.mod.j2 +0 -0
  64. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/go/main.go.j2 +0 -0
  65. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/governance/context-budget.md.j2 +0 -0
  66. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/governance/drift-metrics.md.j2 +0 -0
  67. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/governance/roles.md.j2 +0 -0
  68. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/governance/verification.md.j2 +0 -0
  69. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/governance/workflow.md.j2 +0 -0
  70. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/js/package.json.j2 +0 -0
  71. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/ledger.md.j2 +0 -0
  72. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/python/cli.py.j2 +0 -0
  73. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/python/init.py.j2 +0 -0
  74. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/python/pyproject.toml.j2 +0 -0
  75. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/readme.md.j2 +0 -0
  76. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/rust/Cargo.toml.j2 +0 -0
  77. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/rust/main.rs.j2 +0 -0
  78. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/scripts/exec.cmd.j2 +0 -0
  79. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/scripts/exec.sh.j2 +0 -0
  80. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/scripts/run.cmd.j2 +0 -0
  81. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/scripts/run.sh.j2 +0 -0
  82. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/scripts/setup.cmd.j2 +0 -0
  83. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/scripts/setup.sh.j2 +0 -0
  84. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/templates/workflows/release.yml.j2 +0 -0
  85. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/tools.py +0 -0
  86. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/updater.py +0 -0
  87. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/upgrader.py +0 -0
  88. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/vcs/__init__.py +0 -0
  89. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/vcs/base.py +0 -0
  90. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/vcs/bitbucket.py +0 -0
  91. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/vcs/github.py +0 -0
  92. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/vcs/gitlab.py +0 -0
  93. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith/vcs_commands.py +0 -0
  94. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith.egg-info/dependency_links.txt +0 -0
  95. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith.egg-info/entry_points.txt +0 -0
  96. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith.egg-info/requires.txt +0 -0
  97. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/src/specsmith.egg-info/top_level.txt +0 -0
  98. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/tests/test_auditor.py +0 -0
  99. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/tests/test_compressor.py +0 -0
  100. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/tests/test_importer.py +0 -0
  101. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/tests/test_integrations.py +0 -0
  102. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/tests/test_scaffolder.py +0 -0
  103. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/tests/test_smoke.py +0 -0
  104. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/tests/test_tools.py +0 -0
  105. {specsmith-0.2.2.dev50 → specsmith-0.2.3.dev1}/tests/test_vcs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specsmith
3
- Version: 0.2.2.dev50
3
+ Version: 0.2.3.dev1
4
4
  Summary: Forge governed project scaffolds from the Agentic AI Development Workflow Specification.
5
5
  Author: BitConcepts
6
6
  License: MIT
@@ -129,7 +129,7 @@ Each type gets: tool-aware CI (correct lint/test/security/build tools), domain-s
129
129
  | `import` | Adopt an existing project (merge mode) |
130
130
  | `audit` | Drift detection and health checks (`--fix` to auto-repair) |
131
131
  | `architect` | Interactive architecture generation |
132
- | `validate` | Governance file consistency checks |
132
+ | `validate` | Governance consistency + H11 blocking-loop detection |
133
133
  | `compress` | Archive old ledger entries |
134
134
  | `upgrade` | Update governance to new spec version |
135
135
  | `status` | CI/PR/alert status from VCS platform |
@@ -137,7 +137,13 @@ Each type gets: tool-aware CI (correct lint/test/security/build tools), domain-s
137
137
  | `export` | Compliance report with REQ↔TEST coverage |
138
138
  | `doctor` | Check if verification tools are installed |
139
139
  | `self-update` | Update specsmith (channel-aware) |
140
- | `credits` | AI credit tracking, analysis, and budgets |
140
+ | `credits` | AI credit tracking, analysis, budgets, and rate-limit pacing |
141
+ | `exec` / `ps` / `abort` | Tracked process execution with PID tracking and timeout |
142
+ | `commit` / `push` / `sync` | Governance-aware VCS operations |
143
+ | `branch` / `pr` | Strategy-aware branching and PR creation |
144
+ | `ledger` | Structured ledger add/list/stats |
145
+ | `req` | Requirements list/add/trace/gaps/orphans |
146
+ | `session-end` | End-of-session checklist |
141
147
 
142
148
  ## 7 Agent Integrations
143
149
 
@@ -147,9 +153,36 @@ AGENTS.md (cross-platform standard), Warp/Oz, Claude Code, GitHub Copilot, Curso
147
153
 
148
154
  GitHub Actions, GitLab CI, Bitbucket Pipelines — all with tool-aware CI generated from the verification tool registry. Dependabot/Renovate configured per language ecosystem.
149
155
 
156
+ ## Governance Rules (H1–H12)
157
+
158
+ specsmith-governed projects enforce 12 hard rules. Two were added in v0.2.3 for agentic workflows:
159
+
160
+ - **H11** — Every loop or blocking wait in agent-written scripts must have a deadline, a fallback exit, and a diagnostic message on timeout. `specsmith validate` enforces this automatically.
161
+ - **H12** — On Windows, multi-step automation goes into a `.cmd` file, not inline shell invocations or `.ps1` files.
162
+
163
+ See [Governance Model](https://specsmith.readthedocs.io/en/stable/governance/) for the full rule set.
164
+
165
+ ## Proactive Rate Limit Pacing
166
+
167
+ specsmith ships a rolling-window scheduler that paces AI provider requests before dispatch:
168
+
169
+ - Built-in RPM/TPM profiles for OpenAI, Anthropic, and Google models (including wildcard fallbacks)
170
+ - Pre-dispatch budget check: sleeps until the 60-second window refills instead of overshooting
171
+ - Parses OpenAI-style `"Please try again in 10.793s"` messages and obeys them
172
+ - Adaptive concurrency: halved after a 429, gradually restored after consecutive successes
173
+ - Local overrides always take precedence over built-in defaults
174
+
175
+ ```bash
176
+ specsmith credits limits defaults # list built-in profiles
177
+ specsmith credits limits defaults --install # merge into project config
178
+ specsmith credits limits status --provider openai --model gpt-5.4
179
+ ```
180
+
181
+ See [Rate Limit Pacing](https://specsmith.readthedocs.io/en/stable/rate-limits/) for full details.
182
+
150
183
  ## Documentation
151
184
 
152
- **[specsmith.readthedocs.io](https://specsmith.readthedocs.io)** — Full user manual with tutorials, command reference, project type details, tool registry, governance model, troubleshooting.
185
+ **[specsmith.readthedocs.io](https://specsmith.readthedocs.io)** — Full user manual with tutorials, command reference, project type details, tool registry, governance model, rate-limit pacing, troubleshooting.
153
186
 
154
187
  ## Links
155
188
 
@@ -86,7 +86,7 @@ Each type gets: tool-aware CI (correct lint/test/security/build tools), domain-s
86
86
  | `import` | Adopt an existing project (merge mode) |
87
87
  | `audit` | Drift detection and health checks (`--fix` to auto-repair) |
88
88
  | `architect` | Interactive architecture generation |
89
- | `validate` | Governance file consistency checks |
89
+ | `validate` | Governance consistency + H11 blocking-loop detection |
90
90
  | `compress` | Archive old ledger entries |
91
91
  | `upgrade` | Update governance to new spec version |
92
92
  | `status` | CI/PR/alert status from VCS platform |
@@ -94,7 +94,13 @@ Each type gets: tool-aware CI (correct lint/test/security/build tools), domain-s
94
94
  | `export` | Compliance report with REQ↔TEST coverage |
95
95
  | `doctor` | Check if verification tools are installed |
96
96
  | `self-update` | Update specsmith (channel-aware) |
97
- | `credits` | AI credit tracking, analysis, and budgets |
97
+ | `credits` | AI credit tracking, analysis, budgets, and rate-limit pacing |
98
+ | `exec` / `ps` / `abort` | Tracked process execution with PID tracking and timeout |
99
+ | `commit` / `push` / `sync` | Governance-aware VCS operations |
100
+ | `branch` / `pr` | Strategy-aware branching and PR creation |
101
+ | `ledger` | Structured ledger add/list/stats |
102
+ | `req` | Requirements list/add/trace/gaps/orphans |
103
+ | `session-end` | End-of-session checklist |
98
104
 
99
105
  ## 7 Agent Integrations
100
106
 
@@ -104,9 +110,36 @@ AGENTS.md (cross-platform standard), Warp/Oz, Claude Code, GitHub Copilot, Curso
104
110
 
105
111
  GitHub Actions, GitLab CI, Bitbucket Pipelines — all with tool-aware CI generated from the verification tool registry. Dependabot/Renovate configured per language ecosystem.
106
112
 
113
+ ## Governance Rules (H1–H12)
114
+
115
+ specsmith-governed projects enforce 12 hard rules. Two were added in v0.2.3 for agentic workflows:
116
+
117
+ - **H11** — Every loop or blocking wait in agent-written scripts must have a deadline, a fallback exit, and a diagnostic message on timeout. `specsmith validate` enforces this automatically.
118
+ - **H12** — On Windows, multi-step automation goes into a `.cmd` file, not inline shell invocations or `.ps1` files.
119
+
120
+ See [Governance Model](https://specsmith.readthedocs.io/en/stable/governance/) for the full rule set.
121
+
122
+ ## Proactive Rate Limit Pacing
123
+
124
+ specsmith ships a rolling-window scheduler that paces AI provider requests before dispatch:
125
+
126
+ - Built-in RPM/TPM profiles for OpenAI, Anthropic, and Google models (including wildcard fallbacks)
127
+ - Pre-dispatch budget check: sleeps until the 60-second window refills instead of overshooting
128
+ - Parses OpenAI-style `"Please try again in 10.793s"` messages and obeys them
129
+ - Adaptive concurrency: halved after a 429, gradually restored after consecutive successes
130
+ - Local overrides always take precedence over built-in defaults
131
+
132
+ ```bash
133
+ specsmith credits limits defaults # list built-in profiles
134
+ specsmith credits limits defaults --install # merge into project config
135
+ specsmith credits limits status --provider openai --model gpt-5.4
136
+ ```
137
+
138
+ See [Rate Limit Pacing](https://specsmith.readthedocs.io/en/stable/rate-limits/) for full details.
139
+
107
140
  ## Documentation
108
141
 
109
- **[specsmith.readthedocs.io](https://specsmith.readthedocs.io)** — Full user manual with tutorials, command reference, project type details, tool registry, governance model, troubleshooting.
142
+ **[specsmith.readthedocs.io](https://specsmith.readthedocs.io)** — Full user manual with tutorials, command reference, project type details, tool registry, governance model, rate-limit pacing, troubleshooting.
110
143
 
111
144
  ## Links
112
145
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "specsmith"
7
- version = "0.2.2.dev50"
7
+ version = "0.2.3.dev1"
8
8
  description = "Forge governed project scaffolds from the Agentic AI Development Workflow Specification."
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -8,4 +8,4 @@ from importlib.metadata import version as _pkg_version
8
8
  try:
9
9
  __version__: str = _pkg_version("specsmith")
10
10
  except PackageNotFoundError: # running from source without install
11
- __version__ = "0.2.1"
11
+ __version__ = "0.2.3"
@@ -1501,6 +1501,168 @@ def credits_budget(
1501
1501
  console.print(f" Enabled: {budget.enabled}")
1502
1502
 
1503
1503
 
1504
+ @credits.group(name="limits")
1505
+ def credits_limits() -> None:
1506
+ """Manage persisted per-model RPM/TPM profiles."""
1507
+
1508
+
1509
+ @credits_limits.command(name="list")
1510
+ @click.option("--project-dir", type=click.Path(exists=True), default=".")
1511
+ def credits_limits_list(project_dir: str) -> None:
1512
+ """List configured local model rate-limit profiles."""
1513
+ from specsmith.rate_limits import load_rate_limit_profiles
1514
+
1515
+ root = Path(project_dir).resolve()
1516
+ profiles = sorted(
1517
+ load_rate_limit_profiles(root),
1518
+ key=lambda profile: (profile.provider, profile.model),
1519
+ )
1520
+ if not profiles:
1521
+ console.print("[yellow]No model rate-limit profiles configured.[/yellow]")
1522
+ return
1523
+
1524
+ for profile in profiles:
1525
+ console.print(
1526
+ " "
1527
+ f"{profile.provider}/{profile.model} "
1528
+ f"RPM={profile.rpm_limit} TPM={profile.tpm_limit} "
1529
+ f"target={profile.utilization_target:.2f} "
1530
+ f"concurrency={profile.concurrency_cap}"
1531
+ )
1532
+
1533
+
1534
+ @credits_limits.command(name="set")
1535
+ @click.option("--project-dir", type=click.Path(exists=True), default=".")
1536
+ @click.option("--provider", required=True, help="Provider key, such as openai or anthropic.")
1537
+ @click.option("--model", required=True, help="Model key, such as gpt-5.4.")
1538
+ @click.option("--rpm", "rpm_limit", type=int, required=True, help="Requests per minute limit.")
1539
+ @click.option("--tpm", "tpm_limit", type=int, required=True, help="Tokens per minute limit.")
1540
+ @click.option("--target", "utilization_target", type=float, default=0.7, show_default=True)
1541
+ @click.option("--concurrency", "concurrency_cap", type=int, default=1, show_default=True)
1542
+ def credits_limits_set(
1543
+ project_dir: str,
1544
+ provider: str,
1545
+ model: str,
1546
+ rpm_limit: int,
1547
+ tpm_limit: int,
1548
+ utilization_target: float,
1549
+ concurrency_cap: int,
1550
+ ) -> None:
1551
+ """Create or replace a local model rate-limit profile."""
1552
+ from specsmith.rate_limits import (
1553
+ ModelRateLimitProfile,
1554
+ load_rate_limit_profiles,
1555
+ save_rate_limit_profiles,
1556
+ )
1557
+
1558
+ root = Path(project_dir).resolve()
1559
+ updated_profile = ModelRateLimitProfile(
1560
+ provider=provider,
1561
+ model=model,
1562
+ rpm_limit=rpm_limit,
1563
+ tpm_limit=tpm_limit,
1564
+ utilization_target=utilization_target,
1565
+ concurrency_cap=concurrency_cap,
1566
+ source="override",
1567
+ )
1568
+ profiles = {profile.key: profile for profile in load_rate_limit_profiles(root)}
1569
+ profiles[updated_profile.key] = updated_profile
1570
+ save_rate_limit_profiles(root, list(profiles.values()))
1571
+ console.print(
1572
+ "[green]✓[/green] "
1573
+ f"Saved {updated_profile.provider}/{updated_profile.model} "
1574
+ f"(RPM={updated_profile.rpm_limit}, TPM={updated_profile.tpm_limit}, "
1575
+ f"target={updated_profile.utilization_target:.2f}, "
1576
+ f"concurrency={updated_profile.concurrency_cap})"
1577
+ )
1578
+
1579
+
1580
+ @credits_limits.command(name="status")
1581
+ @click.option("--project-dir", type=click.Path(exists=True), default=".")
1582
+ @click.option("--provider", required=True, help="Provider key, such as openai or anthropic.")
1583
+ @click.option("--model", required=True, help="Model key, such as gpt-5.4.")
1584
+ def credits_limits_status(project_dir: str, provider: str, model: str) -> None:
1585
+ """Show rolling-window snapshot for a model (RPM, TPM, concurrency, moving averages)."""
1586
+ from specsmith.rate_limits import (
1587
+ BUILTIN_PROFILES,
1588
+ load_rate_limit_profiles,
1589
+ load_rate_limit_scheduler,
1590
+ )
1591
+
1592
+ root = Path(project_dir).resolve()
1593
+ profiles = load_rate_limit_profiles(root, defaults=BUILTIN_PROFILES)
1594
+ scheduler = load_rate_limit_scheduler(root, profiles)
1595
+
1596
+ try:
1597
+ snap = scheduler.snapshot(provider, model)
1598
+ except KeyError:
1599
+ console.print(
1600
+ f"[red]No profile found for {provider}/{model}.[/red] "
1601
+ "Use 'specsmith credits limits set' to configure one."
1602
+ )
1603
+ raise SystemExit(1) from None
1604
+
1605
+ console.print(f"[bold]{snap.provider}/{snap.model}[/bold]")
1606
+ console.print(
1607
+ f" RPM: {snap.rolling_request_count} / {snap.effective_rpm_limit} "
1608
+ f"(limit {snap.rpm_limit}, target {snap.effective_rpm_limit})"
1609
+ )
1610
+ console.print(
1611
+ f" TPM: {snap.rolling_token_count:,} / {snap.effective_tpm_limit:,} "
1612
+ f"(limit {snap.tpm_limit:,})"
1613
+ )
1614
+ console.print(
1615
+ f" Utilization: RPM {snap.request_utilization:.1%} TPM {snap.token_utilization:.1%}"
1616
+ )
1617
+ console.print(
1618
+ f" Concurrency: {snap.in_flight} in-flight / {snap.current_concurrency_cap} cap "
1619
+ f"(base {snap.base_concurrency_cap})"
1620
+ )
1621
+ console.print(
1622
+ f" Moving avg: {snap.moving_average_requests:.1f} req/window "
1623
+ f"{snap.moving_average_tokens:,.0f} tok/window"
1624
+ )
1625
+
1626
+
1627
+ @credits_limits.command(name="defaults")
1628
+ @click.option("--project-dir", type=click.Path(exists=True), default=".")
1629
+ @click.option(
1630
+ "--install",
1631
+ is_flag=True,
1632
+ default=False,
1633
+ help="Merge built-in defaults into the local project config (existing overrides preserved).",
1634
+ )
1635
+ def credits_limits_defaults(project_dir: str, install: bool) -> None:
1636
+ """List (or install) built-in RPM/TPM profiles for common provider/model paths."""
1637
+ from specsmith.rate_limits import (
1638
+ BUILTIN_PROFILES,
1639
+ load_rate_limit_profiles,
1640
+ save_rate_limit_profiles,
1641
+ )
1642
+
1643
+ console.print("[bold]Built-in model rate-limit profiles[/bold]")
1644
+ console.print("[dim](conservative defaults — local overrides take precedence)[/dim]\n")
1645
+ for profile in BUILTIN_PROFILES:
1646
+ console.print(
1647
+ f" {profile.provider}/{profile.model:25s} "
1648
+ f"RPM={profile.rpm_limit:<6} TPM={profile.tpm_limit:>12,} "
1649
+ f"target={profile.utilization_target:.2f}"
1650
+ )
1651
+
1652
+ if install:
1653
+ root = Path(project_dir).resolve()
1654
+ # Load existing local profiles; they take precedence over built-ins
1655
+ existing = {p.key: p for p in load_rate_limit_profiles(root)}
1656
+ merged = {p.key: p for p in BUILTIN_PROFILES}
1657
+ merged.update(existing) # local overrides win
1658
+ save_rate_limit_profiles(root, list(merged.values()))
1659
+ added = len(merged) - len(existing)
1660
+ console.print(
1661
+ f"\n[green]\u2713[/green] Installed {added} new default(s) to "
1662
+ ".specsmith/model-rate-limits.json (existing profiles preserved)."
1663
+ )
1664
+
1665
+
1504
1666
  main.add_command(credits)
1505
1667
 
1506
1668