multi-forge 0.2.0__py3-none-any.whl

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 (311) hide show
  1. forge/__init__.py +3 -0
  2. forge/_extensions/agents/.gitkeep +0 -0
  3. forge/_extensions/commands/.gitkeep +0 -0
  4. forge/_extensions/skills/analyze/SKILL.md +87 -0
  5. forge/_extensions/skills/challenge/SKILL.md +91 -0
  6. forge/_extensions/skills/consensus/SKILL.md +120 -0
  7. forge/_extensions/skills/consensus/resources/code_consensus_evaluation.md +94 -0
  8. forge/_extensions/skills/consensus/resources/consensus_evaluation.md +70 -0
  9. forge/_extensions/skills/consensus/resources/synthesis.md +101 -0
  10. forge/_extensions/skills/debate/SKILL.md +116 -0
  11. forge/_extensions/skills/debate/resources/code_debate_evaluation.md +101 -0
  12. forge/_extensions/skills/debate/resources/debate_evaluation.md +90 -0
  13. forge/_extensions/skills/panel/SKILL.md +141 -0
  14. forge/_extensions/skills/panel/resources/synthesis.md +103 -0
  15. forge/_extensions/skills/qa/SKILL.md +704 -0
  16. forge/_extensions/skills/qa/resources/checklist/0-enable.md +78 -0
  17. forge/_extensions/skills/qa/resources/checklist/1-preflight.md +24 -0
  18. forge/_extensions/skills/qa/resources/checklist/10-resume.md +143 -0
  19. forge/_extensions/skills/qa/resources/checklist/11-config.md +150 -0
  20. forge/_extensions/skills/qa/resources/checklist/12-search.md +58 -0
  21. forge/_extensions/skills/qa/resources/checklist/13-guard.md +237 -0
  22. forge/_extensions/skills/qa/resources/checklist/14-workflow.md +305 -0
  23. forge/_extensions/skills/qa/resources/checklist/15-skills.md +155 -0
  24. forge/_extensions/skills/qa/resources/checklist/16-handoff.md +224 -0
  25. forge/_extensions/skills/qa/resources/checklist/17-info.md +50 -0
  26. forge/_extensions/skills/qa/resources/checklist/18-disable.md +84 -0
  27. forge/_extensions/skills/qa/resources/checklist/19-uninstall.md +146 -0
  28. forge/_extensions/skills/qa/resources/checklist/2-extensions.md +188 -0
  29. forge/_extensions/skills/qa/resources/checklist/20-cleanup.md +36 -0
  30. forge/_extensions/skills/qa/resources/checklist/3-auth.md +234 -0
  31. forge/_extensions/skills/qa/resources/checklist/4-proxy.md +481 -0
  32. forge/_extensions/skills/qa/resources/checklist/5-session.md +541 -0
  33. forge/_extensions/skills/qa/resources/checklist/6-hooks.md +275 -0
  34. forge/_extensions/skills/qa/resources/checklist/7-costs.md +309 -0
  35. forge/_extensions/skills/qa/resources/checklist/8-status-line.md +174 -0
  36. forge/_extensions/skills/qa/resources/checklist/9-direct-commands.md +146 -0
  37. forge/_extensions/skills/qa/resources/checklist.md +103 -0
  38. forge/_extensions/skills/qa/resources/report-template.md +62 -0
  39. forge/_extensions/skills/qa/scripts/start-container.sh +529 -0
  40. forge/_extensions/skills/qa/scripts/walkthrough-state.py +1137 -0
  41. forge/_extensions/skills/review/SKILL.md +125 -0
  42. forge/_extensions/skills/review/references/claude-4.6.md +474 -0
  43. forge/_extensions/skills/review/references/claude-4.7.md +710 -0
  44. forge/_extensions/skills/review/references/gemini-3.1.md +546 -0
  45. forge/_extensions/skills/review/references/gpt-5.5.md +490 -0
  46. forge/_extensions/skills/review/references/skills-writing-guide.md +1588 -0
  47. forge/_extensions/skills/review/resources/code-anthropic.md +160 -0
  48. forge/_extensions/skills/review/resources/code-gemini.md +184 -0
  49. forge/_extensions/skills/review/resources/code-openai.md +203 -0
  50. forge/_extensions/skills/review/resources/code.md +160 -0
  51. forge/_extensions/skills/review-docs/SKILL.md +121 -0
  52. forge/_extensions/skills/review-docs/resources/docs-anthropic.md +170 -0
  53. forge/_extensions/skills/review-docs/resources/docs-gemini.md +204 -0
  54. forge/_extensions/skills/review-docs/resources/docs-openai.md +231 -0
  55. forge/_extensions/skills/review-docs/resources/docs.md +170 -0
  56. forge/_extensions/skills/smoke-test/SKILL.md +27 -0
  57. forge/_extensions/skills/smoke-test/scripts/smoke-test.sh +118 -0
  58. forge/_extensions/skills/understand/SKILL.md +148 -0
  59. forge/_extensions/skills/understand/resources/code-anthropic.md +163 -0
  60. forge/_extensions/skills/understand/resources/code-gemini.md +194 -0
  61. forge/_extensions/skills/understand/resources/code-openai.md +181 -0
  62. forge/_extensions/skills/understand/resources/code.md +163 -0
  63. forge/_extensions/skills/understand/resources/docs-anthropic.md +177 -0
  64. forge/_extensions/skills/understand/resources/docs-gemini.md +202 -0
  65. forge/_extensions/skills/understand/resources/docs-openai.md +191 -0
  66. forge/_extensions/skills/understand/resources/docs.md +177 -0
  67. forge/_extensions/skills/walkthrough/SKILL.md +599 -0
  68. forge/_extensions/skills/walkthrough/resources/checklist.md +765 -0
  69. forge/_extensions/skills/walkthrough/scripts/run-in-repo.sh +118 -0
  70. forge/_extensions/skills/walkthrough/scripts/setup-test-repo.sh +198 -0
  71. forge/_extensions/skills/walkthrough/scripts/walkthrough-state.py +1137 -0
  72. forge/backend/__init__.py +174 -0
  73. forge/backend/adapters/__init__.py +38 -0
  74. forge/backend/adapters/litellm.py +158 -0
  75. forge/backend/creation.py +89 -0
  76. forge/backend/registry.py +178 -0
  77. forge/cli/__init__.py +16 -0
  78. forge/cli/auth.py +483 -0
  79. forge/cli/backend.py +298 -0
  80. forge/cli/claude.py +411 -0
  81. forge/cli/config_cmd.py +303 -0
  82. forge/cli/extensions.py +1001 -0
  83. forge/cli/gc.py +165 -0
  84. forge/cli/guard.py +1018 -0
  85. forge/cli/guards.py +106 -0
  86. forge/cli/handoff.py +110 -0
  87. forge/cli/hooks/__init__.py +36 -0
  88. forge/cli/hooks/_group.py +20 -0
  89. forge/cli/hooks/_helpers.py +149 -0
  90. forge/cli/hooks/commands.py +1677 -0
  91. forge/cli/hooks/direct_commands.py +1304 -0
  92. forge/cli/hooks/install.py +232 -0
  93. forge/cli/hooks/policy.py +151 -0
  94. forge/cli/hooks/read_hygiene.py +74 -0
  95. forge/cli/hooks/verification.py +370 -0
  96. forge/cli/logs.py +406 -0
  97. forge/cli/main.py +292 -0
  98. forge/cli/proxy.py +1821 -0
  99. forge/cli/proxy_costs.py +313 -0
  100. forge/cli/search.py +416 -0
  101. forge/cli/session.py +892 -0
  102. forge/cli/session_addendum.py +81 -0
  103. forge/cli/session_fork.py +750 -0
  104. forge/cli/session_handoff.py +141 -0
  105. forge/cli/session_lifecycle.py +2053 -0
  106. forge/cli/session_manage.py +1336 -0
  107. forge/cli/session_memory.py +201 -0
  108. forge/cli/status_line.py +1398 -0
  109. forge/cli/workflow.py +1964 -0
  110. forge/config/__init__.py +110 -0
  111. forge/config/dataclass_utils.py +88 -0
  112. forge/config/defaults/__init__.py +0 -0
  113. forge/config/defaults/backends/__init__.py +0 -0
  114. forge/config/defaults/backends/litellm.yaml +196 -0
  115. forge/config/defaults/templates/__init__.py +0 -0
  116. forge/config/defaults/templates/litellm-anthropic-local.yaml +33 -0
  117. forge/config/defaults/templates/litellm-anthropic.yaml +24 -0
  118. forge/config/defaults/templates/litellm-gemini-flash-local.yaml +37 -0
  119. forge/config/defaults/templates/litellm-gemini-local.yaml +32 -0
  120. forge/config/defaults/templates/litellm-gemini-test.yaml +34 -0
  121. forge/config/defaults/templates/litellm-gemini.yaml +21 -0
  122. forge/config/defaults/templates/litellm-openai-codex-local.yaml +36 -0
  123. forge/config/defaults/templates/litellm-openai-local.yaml +38 -0
  124. forge/config/defaults/templates/litellm-openai.yaml +28 -0
  125. forge/config/defaults/templates/openrouter-anthropic.yaml +23 -0
  126. forge/config/defaults/templates/openrouter-deepseek.yaml +26 -0
  127. forge/config/defaults/templates/openrouter-gemini-flash.yaml +26 -0
  128. forge/config/defaults/templates/openrouter-gemini.yaml +23 -0
  129. forge/config/defaults/templates/openrouter-glm.yaml +23 -0
  130. forge/config/defaults/templates/openrouter-kimi.yaml +30 -0
  131. forge/config/defaults/templates/openrouter-minimax.yaml +26 -0
  132. forge/config/defaults/templates/openrouter-openai-codex.yaml +23 -0
  133. forge/config/defaults/templates/openrouter-openai.yaml +28 -0
  134. forge/config/defaults/templates/openrouter-qwen.yaml +25 -0
  135. forge/config/loader.py +675 -0
  136. forge/config/schema.py +448 -0
  137. forge/core/__init__.py +5 -0
  138. forge/core/auth/__init__.py +67 -0
  139. forge/core/auth/capabilities.py +219 -0
  140. forge/core/auth/credentials_file.py +244 -0
  141. forge/core/auth/protocols.py +18 -0
  142. forge/core/auth/secrets.py +243 -0
  143. forge/core/auth/template_secrets.py +112 -0
  144. forge/core/data/__init__.py +5 -0
  145. forge/core/data/model_catalog.yaml +1522 -0
  146. forge/core/data/pricing.yaml +140 -0
  147. forge/core/data/system_prompt_addendums/__init__.py +0 -0
  148. forge/core/data/system_prompt_addendums/gemini.md +330 -0
  149. forge/core/data/system_prompt_addendums/openai.md +328 -0
  150. forge/core/llm/__init__.py +231 -0
  151. forge/core/llm/clients/__init__.py +14 -0
  152. forge/core/llm/clients/base.py +115 -0
  153. forge/core/llm/clients/litellm.py +619 -0
  154. forge/core/llm/clients/openai_compat.py +244 -0
  155. forge/core/llm/clients/openrouter.py +234 -0
  156. forge/core/llm/credentials.py +439 -0
  157. forge/core/llm/detection.py +86 -0
  158. forge/core/llm/errors.py +44 -0
  159. forge/core/llm/protocols.py +80 -0
  160. forge/core/llm/types.py +176 -0
  161. forge/core/logging.py +146 -0
  162. forge/core/models/__init__.py +91 -0
  163. forge/core/models/catalog.py +467 -0
  164. forge/core/models/pricing.py +165 -0
  165. forge/core/models/types.py +167 -0
  166. forge/core/naming.py +212 -0
  167. forge/core/ops/__init__.py +73 -0
  168. forge/core/ops/context.py +141 -0
  169. forge/core/ops/gc.py +802 -0
  170. forge/core/ops/proxy.py +146 -0
  171. forge/core/ops/resolution.py +135 -0
  172. forge/core/ops/session.py +344 -0
  173. forge/core/ops/session_context.py +548 -0
  174. forge/core/paths.py +38 -0
  175. forge/core/process.py +54 -0
  176. forge/core/reactive/__init__.py +38 -0
  177. forge/core/reactive/cost_tracking.py +300 -0
  178. forge/core/reactive/env.py +180 -0
  179. forge/core/reactive/proxy.py +78 -0
  180. forge/core/reactive/routing.py +622 -0
  181. forge/core/reactive/session_runner.py +185 -0
  182. forge/core/reactive/structured_output.py +62 -0
  183. forge/core/reactive/tagger.py +94 -0
  184. forge/core/reactive/throttle.py +132 -0
  185. forge/core/state/__init__.py +59 -0
  186. forge/core/state/exceptions.py +59 -0
  187. forge/core/state/io.py +140 -0
  188. forge/core/state/lock.py +99 -0
  189. forge/core/state/timestamps.py +60 -0
  190. forge/core/transcript.py +78 -0
  191. forge/core/typing_helpers.py +24 -0
  192. forge/core/workqueue/__init__.py +67 -0
  193. forge/core/workqueue/queue.py +552 -0
  194. forge/core/workqueue/types.py +63 -0
  195. forge/guard/__init__.py +26 -0
  196. forge/guard/deterministic/__init__.py +26 -0
  197. forge/guard/deterministic/base.py +158 -0
  198. forge/guard/deterministic/coding_standards.py +256 -0
  199. forge/guard/deterministic/registry.py +148 -0
  200. forge/guard/deterministic/tdd.py +171 -0
  201. forge/guard/engine.py +216 -0
  202. forge/guard/protocols.py +91 -0
  203. forge/guard/queries.py +96 -0
  204. forge/guard/semantic/__init__.py +34 -0
  205. forge/guard/semantic/promotion.py +18 -0
  206. forge/guard/semantic/supervisor.py +813 -0
  207. forge/guard/semantic/verdict.py +183 -0
  208. forge/guard/store.py +124 -0
  209. forge/guard/team/__init__.py +6 -0
  210. forge/guard/team/config.py +24 -0
  211. forge/guard/team/handlers.py +209 -0
  212. forge/guard/team/prompts.py +41 -0
  213. forge/guard/types.py +125 -0
  214. forge/guard/workflow/__init__.py +17 -0
  215. forge/guard/workflow/branches.py +67 -0
  216. forge/guard/workflow/config.py +63 -0
  217. forge/guard/workflow/divergence.py +113 -0
  218. forge/guard/workflow/policy.py +87 -0
  219. forge/guard/workflow/stages.py +205 -0
  220. forge/install/__init__.py +55 -0
  221. forge/install/cli.py +281 -0
  222. forge/install/exceptions.py +163 -0
  223. forge/install/hooks.py +109 -0
  224. forge/install/installer.py +1037 -0
  225. forge/install/models.py +321 -0
  226. forge/install/preset.py +272 -0
  227. forge/install/settings_merge.py +831 -0
  228. forge/install/tracking.py +238 -0
  229. forge/install/version.py +141 -0
  230. forge/proxy/__init__.py +0 -0
  231. forge/proxy/base_client.py +181 -0
  232. forge/proxy/client_adapter.py +476 -0
  233. forge/proxy/client_factory.py +531 -0
  234. forge/proxy/converters.py +1206 -0
  235. forge/proxy/cost_logger.py +132 -0
  236. forge/proxy/cost_tracker.py +242 -0
  237. forge/proxy/data_models.py +338 -0
  238. forge/proxy/error_hints.py +92 -0
  239. forge/proxy/metrics.py +222 -0
  240. forge/proxy/model_spec.py +158 -0
  241. forge/proxy/proxies.py +333 -0
  242. forge/proxy/proxy_identity.py +134 -0
  243. forge/proxy/proxy_orchestrator.py +1018 -0
  244. forge/proxy/proxy_startup.py +54 -0
  245. forge/proxy/server.py +1561 -0
  246. forge/proxy/utils.py +537 -0
  247. forge/review/__init__.py +6 -0
  248. forge/review/adversarial.py +111 -0
  249. forge/review/consensus.py +236 -0
  250. forge/review/engine.py +356 -0
  251. forge/review/models.py +437 -0
  252. forge/review/resources/__init__.py +5 -0
  253. forge/review/resources/codereview-performance.md +85 -0
  254. forge/review/resources/codereview-quick.md +75 -0
  255. forge/review/resources/codereview-security.md +92 -0
  256. forge/review/resources/codereview.md +85 -0
  257. forge/review/resources/docreview-quick.md +75 -0
  258. forge/review/resources/docreview.md +86 -0
  259. forge/review/resources/thinkdeep.md +89 -0
  260. forge/review/routing.py +368 -0
  261. forge/review/synthesis.py +73 -0
  262. forge/runtime_config.py +438 -0
  263. forge/search/__init__.py +55 -0
  264. forge/search/bm25_store.py +264 -0
  265. forge/search/content_store.py +197 -0
  266. forge/search/engine.py +352 -0
  267. forge/search/exceptions.py +51 -0
  268. forge/search/extractor.py +234 -0
  269. forge/search/index_state.py +295 -0
  270. forge/search/store.py +215 -0
  271. forge/search/tokenizer.py +24 -0
  272. forge/session/__init__.py +130 -0
  273. forge/session/active.py +339 -0
  274. forge/session/artifacts.py +202 -0
  275. forge/session/claude/__init__.py +50 -0
  276. forge/session/claude/cleanup.py +105 -0
  277. forge/session/claude/invoke.py +236 -0
  278. forge/session/claude/paths.py +200 -0
  279. forge/session/cleanup.py +216 -0
  280. forge/session/config.py +34 -0
  281. forge/session/direct_model.py +107 -0
  282. forge/session/effective.py +169 -0
  283. forge/session/exceptions.py +255 -0
  284. forge/session/handoff.py +881 -0
  285. forge/session/handoff_agent.py +544 -0
  286. forge/session/hooks/__init__.py +35 -0
  287. forge/session/hooks/models.py +73 -0
  288. forge/session/hooks/session_start.py +507 -0
  289. forge/session/identity.py +84 -0
  290. forge/session/index.py +553 -0
  291. forge/session/manager.py +1506 -0
  292. forge/session/models.py +572 -0
  293. forge/session/overrides.py +344 -0
  294. forge/session/plan_resolution.py +286 -0
  295. forge/session/prev_sessions.py +128 -0
  296. forge/session/store.py +431 -0
  297. forge/session/validation.py +47 -0
  298. forge/session/worktree/__init__.py +65 -0
  299. forge/session/worktree/cleanup.py +262 -0
  300. forge/session/worktree/config_copy.py +203 -0
  301. forge/session/worktree/create.py +332 -0
  302. forge/sidecar/__init__.py +29 -0
  303. forge/sidecar/container.py +161 -0
  304. forge/sidecar/docker.py +86 -0
  305. forge/sidecar/secrets.py +19 -0
  306. multi_forge-0.2.0.dist-info/METADATA +242 -0
  307. multi_forge-0.2.0.dist-info/RECORD +311 -0
  308. multi_forge-0.2.0.dist-info/WHEEL +4 -0
  309. multi_forge-0.2.0.dist-info/entry_points.txt +2 -0
  310. multi_forge-0.2.0.dist-info/licenses/LICENSE +203 -0
  311. multi_forge-0.2.0.dist-info/licenses/NOTICE +14 -0
@@ -0,0 +1,234 @@
1
+ <!-- prereq: 0.3 -->
2
+
3
+ ## 3. Authentication (`forge authentication`)
4
+
5
+ Tests credential storage/resolution in `$FORGE_HOME/credentials.yaml` (named profiles).
6
+
7
+ > **Note:** These steps test Forge's credential management UI only. The keys stored via `forge authentication login` are
8
+ > NOT used by the proxy or Claude Code. The proxy gets its backend keys from environment variables injected at container
9
+ > start (`/etc/profile.d/forge-qa.sh`). You can use placeholder values (e.g., `sk-ant-manual-test-12345`) for all login
10
+ > prompts.
11
+
12
+ ### 3.1 Login — Store Credentials
13
+
14
+ <!-- human:guided -->
15
+
16
+ In a **shell inside the QA container** (`docker exec -it $CONTAINER bash -l` — the container name is printed by
17
+ `start-container.sh`, usually `forge-qa`), run the command below. Enter a test API key when prompted (e.g.,
18
+ `sk-ant-manual-test-12345`). Input will be hidden.
19
+
20
+ ```
21
+ # Store credentials for a single credential
22
+ forge authentication login -c anthropic-api
23
+
24
+ # Expected: prompts for ANTHROPIC_API_KEY (input hidden)
25
+ # Enter a test key, e.g.: sk-ant-manual-test-12345
26
+ ```
27
+
28
+ - [ ] Prompts for API key with hidden input (characters not echoed)
29
+ - [ ] Shows "Credentials saved to $FORGE_HOME/credentials.yaml"
30
+ - [ ] File created with 0o600 permissions (`ls -la $FORGE_HOME/credentials.yaml`)
31
+
32
+ ### 3.2 Login — Named Profiles
33
+
34
+ <!-- human:guided -->
35
+
36
+ In the **container shell**, store credentials under a named profile. Enter a different test key (e.g.,
37
+ `sk-ant-work-key-99999`).
38
+
39
+ ```
40
+ # Store credentials in a named profile
41
+ forge authentication login -c anthropic-api --profile work
42
+ # Enter a different key, e.g.: sk-ant-work-key-99999
43
+
44
+ # Verify both profiles exist
45
+ forge authentication profiles
46
+ ```
47
+
48
+ - [ ] `work` profile created separately from `default`
49
+ - [ ] `forge authentication profiles` shows both profiles with key counts
50
+ - [ ] Active profile marked with "← active"
51
+
52
+ ### 3.3 Login — Keep Existing Values
53
+
54
+ <!-- human:guided -->
55
+
56
+ In the **container shell**, re-run login for the same credential. The existing value appears as a masked default (e.g.,
57
+ `ANTHROPIC_API_KEY [sk-a…5678]`). Press Enter to keep it.
58
+
59
+ ```
60
+ # Re-run login for same credential — existing value shown as masked default
61
+ forge authentication login -c anthropic-api
62
+
63
+ # Expected: shows existing value like "ANTHROPIC_API_KEY [sk-a…5678]"
64
+ # Press Enter to keep existing value
65
+ ```
66
+
67
+ - [ ] Existing value shown as masked default (first 4 + last 4 chars)
68
+ - [ ] Pressing Enter preserves the existing value (not overwritten)
69
+
70
+ ### 3.4 Status — Dual-View Output
71
+
72
+ <!-- auto -->
73
+
74
+ ```bash
75
+ # Check credential status
76
+ forge authentication status
77
+
78
+ # Expected output has two sections:
79
+ # Configured capabilities:
80
+ # * anthropic-api ... (file:default)
81
+ #
82
+ # Credential details:
83
+ # anthropic-api
84
+ # * ANTHROPIC_API_KEY = sk-a…5678 (file:default)
85
+ ```
86
+
87
+ - [ ] Shows "Configured capabilities:" section with configured credentials
88
+ - [ ] Shows "Not configured (set up if needed):" section for unconfigured credentials
89
+ - [ ] Shows "Credential details:" section with per-variable source attribution
90
+ - [ ] Values are masked (never shown in full)
91
+ - [ ] All 5 credentials displayed (openrouter, anthropic-api, openai-api, gemini-api, litellm-remote)
92
+ - [ ] Unconfigured credentials show "not configured" (not "MISSING")
93
+
94
+ ### 3.5 Status — Env Overrides File
95
+
96
+ <!-- auto -->
97
+
98
+ ```bash
99
+ # Set env var that also exists in file
100
+ export ANTHROPIC_API_KEY=sk-ant-from-env-override
101
+ forge authentication status
102
+
103
+ # Expected: shows (env) source, not (file:default)
104
+ unset ANTHROPIC_API_KEY
105
+ ```
106
+
107
+ - [ ] Env var takes precedence — shown as `(env)` not `(file:default)`
108
+ - [ ] After unsetting env var, status shows `(file:default)` again
109
+
110
+ ### 3.6 Status — Named Profile
111
+
112
+ <!-- auto -->
113
+
114
+ ```bash
115
+ # Check status for a specific profile
116
+ forge authentication status --profile work
117
+ ```
118
+
119
+ - [ ] Shows `(file:work)` for keys stored in work profile
120
+ - [ ] Keys not in work profile shown as "not configured" (even if in default)
121
+
122
+ ### 3.7 Profiles — List and Active Marker
123
+
124
+ <!-- auto -->
125
+
126
+ ```bash
127
+ # List all profiles
128
+ forge authentication profiles
129
+
130
+ # Change active profile via env var
131
+ FORGE_PROFILE=work forge authentication profiles
132
+ ```
133
+
134
+ - [ ] Shows profile names with key counts
135
+ - [ ] Default profile marked "← active" by default
136
+ - [ ] `FORGE_PROFILE=work` changes active marker to work
137
+
138
+ ### 3.8 Logout — Remove Profile
139
+
140
+ <!-- auto -->
141
+
142
+ ```bash
143
+ # Remove a profile (with confirmation)
144
+ printf 'y\n' | forge authentication logout --profile work
145
+
146
+ # Verify removed
147
+ forge authentication profiles
148
+ ```
149
+
150
+ - [ ] Confirmation prompt shown (unless `-y` used)
151
+ - [ ] Profile removed from credentials file
152
+ - [ ] Other profiles unaffected
153
+
154
+ ### 3.9 Logout — Skip Confirmation
155
+
156
+ <!-- human:guided -->
157
+
158
+ In the **container shell**, create a temp profile (enter any test key when prompted), then remove it with `-y`.
159
+
160
+ ```
161
+ # Re-create and remove without confirmation
162
+ forge authentication login -c anthropic-api --profile temp
163
+ # Enter any test key
164
+
165
+ forge authentication logout --profile temp -y
166
+ forge authentication profiles
167
+ ```
168
+
169
+ - [ ] `-y` flag skips confirmation
170
+ - [ ] Profile removed immediately
171
+
172
+ ### 3.10 Credential File Security
173
+
174
+ <!-- auto -->
175
+
176
+ ```bash
177
+ # Check file permissions
178
+ ls -la $FORGE_HOME/credentials.yaml
179
+ # Expected: -rw------- (0o600)
180
+
181
+ # Check file content structure without exposing secrets
182
+ python3 -c "
183
+ import yaml, sys
184
+ with open('$FORGE_HOME/credentials.yaml') as f:
185
+ data = yaml.safe_load(f)
186
+ print('version:', data.get('version'))
187
+ print('profiles:', list(data.get('profiles', {}).keys()))
188
+ for name, profile in data.get('profiles', {}).items():
189
+ masked = {k: v[:6] + '...' if isinstance(v, str) and len(v) > 6 else v for k, v in profile.items()}
190
+ print(f' {name}: {masked}')
191
+ "
192
+ ```
193
+
194
+ - [ ] Permissions are 0o600 (owner read/write only)
195
+ - [ ] File has `version: 1` field
196
+ - [ ] Profile names map to flat key-value pairs (values masked)
197
+
198
+ ### 3.11 Credential Resolution in CredentialManager
199
+
200
+ <!-- auto -->
201
+
202
+ ```bash
203
+ # Verify file-based credentials are actually used by the system
204
+ # Unset env var, rely on file-based credential
205
+ unset ANTHROPIC_API_KEY
206
+
207
+ # If you have a valid key stored via forge authentication login:
208
+ # Starting a session or proxy that needs ANTHROPIC_API_KEY should work
209
+ # without the env var set (it reads from $FORGE_HOME/credentials.yaml)
210
+ forge authentication status --profile default
211
+ # Should show: * ANTHROPIC_API_KEY = sk-a…xxxx (file:default)
212
+ ```
213
+
214
+ - [ ] Credential available via file when env var is unset
215
+ - [ ] `forge authentication status` confirms file source
216
+
217
+ ### 3.12 Retired Credential Names
218
+
219
+ <!-- auto -->
220
+
221
+ ```bash
222
+ # Old 'anthropic' name should produce migration guidance
223
+ forge authentication login -c anthropic 2>&1 || true
224
+ # Expected: exit 1, yellow message mentioning 'anthropic-api'
225
+
226
+ # Old 'litellm-local' name should explain it's not a credential
227
+ forge authentication login -c litellm-local 2>&1 || true
228
+ # Expected: exit 1, message mentioning gemini-api, openai-api, anthropic-api
229
+ ```
230
+
231
+ - [ ] `anthropic` produces clear migration message pointing to `anthropic-api`
232
+ - [ ] `litellm-local` explains it's not a credential and lists provider alternatives
233
+
234
+ ---
@@ -0,0 +1,481 @@
1
+ <!-- prereq: 0.3 -->
2
+
3
+ ## 4. Proxy Management
4
+
5
+ Default QA runs this section through the OpenRouter provider profile. It requires the selected provider credentials
6
+ (`OPENROUTER_API_KEY` by default), not remote LiteLLM infrastructure unless QA was started with
7
+ `--provider-profile remote-litellm`. LiteLLM template list/show checks below are metadata-only.
8
+
9
+ ### 4.1 List Proxies and Templates
10
+
11
+ <!-- auto -->
12
+
13
+ ```bash
14
+ # List existing proxies (none yet)
15
+ forge proxy list
16
+
17
+ # Expected: "No proxies found." + tip to run 'forge proxy template list'
18
+ # Templates are listed via the template subcommand, not inline in proxy list.
19
+
20
+ # List available templates
21
+ forge proxy template list
22
+ ```
23
+
24
+ - [ ] `forge proxy list` shows "No proxies found." when none exist
25
+ - [ ] `forge proxy list` shows tip to run `forge proxy template list`
26
+ - [ ] `forge proxy template list` shows available templates (18 user-facing: litellm-anthropic, litellm-anthropic-local,
27
+ litellm-gemini, litellm-gemini-flash-local, litellm-gemini-local, litellm-openai, litellm-openai-codex-local,
28
+ litellm-openai-local, openrouter-anthropic, openrouter-deepseek, openrouter-gemini, openrouter-gemini-flash,
29
+ openrouter-glm, openrouter-kimi, openrouter-minimax, openrouter-openai, openrouter-openai-codex, openrouter-qwen)
30
+ - [ ] Internal test-only templates (e.g., litellm-gemini-test) are hidden from the default list
31
+
32
+ ### 4.2 Create a Proxy
33
+
34
+ <!-- auto -->
35
+
36
+ ```bash
37
+ # Clean up from previous runs
38
+ forge proxy delete "$FORGE_QA_GEMINI_PROXY" --force 2>/dev/null || true
39
+ forge proxy delete "$FORGE_QA_OPENAI_PROXY" --force 2>/dev/null || true
40
+ forge proxy delete "$FORGE_QA_ANTHROPIC_PROXY" --force 2>/dev/null || true
41
+ forge proxy delete openrouter-gemini --force 2>/dev/null || true
42
+ forge proxy delete openrouter-openai --force 2>/dev/null || true
43
+ forge proxy delete openrouter-deepseek --force 2>/dev/null || true
44
+ forge proxy delete openrouter-minimax --force 2>/dev/null || true
45
+ forge proxy delete test-proxy-nostart --force 2>/dev/null || true
46
+
47
+ # Create named role proxies used by downstream session/review steps.
48
+ forge proxy create "$FORGE_QA_GEMINI_TEMPLATE" --name "$FORGE_QA_GEMINI_PROXY"
49
+
50
+ # Create a named review proxy with per-tier overrides
51
+ forge proxy create "$FORGE_QA_OPENAI_TEMPLATE" --name "$FORGE_QA_OPENAI_PROXY" --opus-reasoning high
52
+
53
+ # Create workflow-default aliases so section 14 exercises production default proxy IDs.
54
+ # In remote-litellm profile these names intentionally point at remote LiteLLM-backed proxies.
55
+ forge proxy create "$FORGE_QA_GEMINI_TEMPLATE" --no-start --name openrouter-gemini
56
+ forge proxy create "$FORGE_QA_OPENAI_TEMPLATE" --no-start --name openrouter-openai
57
+
58
+ # Workflow proxies for cheap models (openrouter profile only).
59
+ # Started directly with canonical names so models.py proxy lookup matches the running port.
60
+ if [ -n "${FORGE_QA_DEEPSEEK_TEMPLATE:-}" ]; then
61
+ forge proxy create "$FORGE_QA_DEEPSEEK_TEMPLATE" --name openrouter-deepseek
62
+ fi
63
+ if [ -n "${FORGE_QA_MINIMAX_TEMPLATE:-}" ]; then
64
+ forge proxy create "$FORGE_QA_MINIMAX_TEMPLATE" --name openrouter-minimax
65
+ fi
66
+
67
+ # Create config only (don't start the server)
68
+ forge proxy create "$FORGE_QA_OPENAI_TEMPLATE" --no-start --name test-proxy-nostart
69
+
70
+ # List proxies again
71
+ forge proxy list
72
+ ```
73
+
74
+ - [ ] Named role proxies from `$FORGE_QA_*_TEMPLATE` created successfully (or note if skipped)
75
+ - [ ] Named role proxies, workflow aliases, and `test-proxy-nostart` appear in the list with expected base_url/port
76
+ information
77
+ - [ ] Per-tier overrides applied to `$FORGE_QA_OPENAI_PROXY`
78
+ - [ ] Workflow proxies (`openrouter-deepseek`, `openrouter-minimax`) started for openrouter profile (or skipped for
79
+ remote-litellm)
80
+
81
+ ### 4.3 Show Proxy Details
82
+
83
+ <!-- prereq: 4.2 -->
84
+
85
+ <!-- auto -->
86
+
87
+ ```bash
88
+ # Show details of a specific proxy (created in 4.2)
89
+ forge proxy show test-proxy-nostart
90
+ ```
91
+
92
+ - [ ] Shows template, base_url, tier mappings
93
+ - [ ] Shows proxy configuration YAML (port, tiers, provider settings)
94
+
95
+ ### 4.4 Proxy Edit and Validate
96
+
97
+ <!-- prereq: 4.2 -->
98
+
99
+ <!-- human:guided -->
100
+
101
+ In the **container shell**, run these commands to view, edit, validate, and delete a proxy. The `edit` command opens
102
+ `$EDITOR` — verify it launches.
103
+
104
+ ```
105
+ # View proxy config
106
+ forge proxy show <proxy_id>
107
+
108
+ # Edit proxy config (opens in $EDITOR)
109
+ forge proxy edit <proxy_id>
110
+
111
+ # Validate proxy config
112
+ forge proxy validate <proxy_id>
113
+
114
+ # Delete a proxy
115
+ forge proxy delete <proxy_id>
116
+ ```
117
+
118
+ - [ ] `show` displays full proxy configuration
119
+ - [ ] `edit` opens proxy.yaml in editor
120
+ - [ ] `validate` reports config health
121
+ - [ ] `delete` removes proxy and cleans up registry
122
+
123
+ ### 4.5 Proxy Clean
124
+
125
+ <!-- auto -->
126
+
127
+ ```bash
128
+ # Clean up stale proxies (dead processes)
129
+ forge proxy clean
130
+ ```
131
+
132
+ - [ ] Clean removes stale entries (or reports none found)
133
+
134
+ ### 4.6 Launch Session with Host Proxy
135
+
136
+ <!-- prereq: 2.4, 4.2 -->
137
+
138
+ <!-- requires: api_key -->
139
+
140
+ <!-- human:guided -->
141
+
142
+ In the **container shell**, create a session bound to a proxy, then launch Claude through the proxy.
143
+
144
+ ```
145
+ # Clean up from previous runs
146
+ forge session delete proxy-session --force 2>/dev/null || true
147
+
148
+ # Create a session bound to the proxy created in 4.2 (accepts proxy_id or template name)
149
+ forge session start proxy-session --proxy "$FORGE_QA_OPENAI_PROXY" --no-launch
150
+
151
+ # Verify session recorded proxy identity
152
+ cat .forge/sessions/proxy-session/forge.session.json | jq '.intent.proxy'
153
+ # Should show template and base_url fields
154
+ ```
155
+
156
+ - [ ] `--proxy` binds session to the named proxy
157
+ - [ ] Session manifest `.intent.proxy` shows template and base_url
158
+
159
+ Now launch Claude through the named proxy. This opens an interactive Claude session — exit with Ctrl-C or `/exit` when
160
+ done verifying.
161
+
162
+ ```
163
+ # Launch Claude through the running proxy created in 4.2
164
+ forge claude start --proxy "$FORGE_QA_OPENAI_PROXY" -- --debug
165
+ # Claude should start with ANTHROPIC_BASE_URL pointing to the proxy
166
+ # Verify by checking the status line or running: echo $ANTHROPIC_BASE_URL inside Claude
167
+ # Exit Claude when done (Ctrl-C or /exit)
168
+ ```
169
+
170
+ - [ ] `forge claude start --proxy` starts Claude routed through the named proxy
171
+ - [ ] Status line shows proxy info (template, tier mappings) when running with proxy
172
+
173
+ ### 4.7 Live % Commands in Proxy Session
174
+
175
+ <!-- prereq: 2.4, 4.2 -->
176
+
177
+ <!-- requires: api_key -->
178
+
179
+ <!-- human:guided -->
180
+
181
+ Now launch Claude (or reuse the session from 4.6):
182
+
183
+ ```
184
+ forge claude start --proxy "$FORGE_QA_OPENAI_PROXY" -- --debug
185
+ ```
186
+
187
+ In the **live Claude session**, type these prompts:
188
+
189
+ ```
190
+ %help
191
+ %session list
192
+ %proxy list
193
+ %proxy show qa-openai
194
+ ```
195
+
196
+ - [ ] `%help` returns help text listing available `%` commands
197
+ - [ ] `%session list` shows sessions (including proxy-session)
198
+ - [ ] `%proxy list` shows proxies from inside the session
199
+ - [ ] `%proxy show` displays proxy details (template, tier mappings)
200
+ - [ ] Commands are intercepted by `UserPromptSubmit` hook (not passed to Claude as prompts)
201
+
202
+ Exit the Claude session when done.
203
+
204
+ ### 4.8 Proxy Delete UX (Confirmation + Smart-Pointer Semantics)
205
+
206
+ <!-- prereq: 4.2, 4.6 -->
207
+
208
+ <!-- human:guided -->
209
+
210
+ Test that `forge proxy delete` requires confirmation, shows the related shared-port items, and uses smart-pointer
211
+ semantics (only kills the server when deleting the last registry entry for that port).
212
+
213
+ In the **container shell**:
214
+
215
+ ```
216
+ # Clean up from previous runs
217
+ forge proxy delete delete-test-proxy --force 2>/dev/null || true
218
+
219
+ # Create an alias on the same shared port as the QA OpenAI proxy
220
+ forge proxy create "$FORGE_QA_OPENAI_TEMPLATE" --no-start --name delete-test-proxy
221
+
222
+ # Try to delete the alias -- should prompt for confirmation and list the related proxy entry
223
+ forge proxy delete delete-test-proxy
224
+ # Choose N to cancel
225
+ # Expected:
226
+ # - confirmation prompt appears
227
+ # - related proxies on the same port are listed (including qa-openai)
228
+ # - no false warning about proxy-session/proxy-session-url just because they share port 8085
229
+
230
+ # Verify alias still exists after cancelling
231
+ forge proxy list
232
+ # Expected: delete-test-proxy still listed
233
+
234
+ # Now confirm deletion of the alias
235
+ forge proxy delete delete-test-proxy
236
+ # Choose y to confirm
237
+ # Expected:
238
+ # - "Deleted proxy 'delete-test-proxy'"
239
+ # - shared server references are kept alive via qa-openai
240
+
241
+ # Verify alias gone but qa-openai still present
242
+ forge proxy list
243
+
244
+ # Finally test deleting the primary QA proxy while shared-port aliases remain
245
+ forge proxy delete "$FORGE_QA_OPENAI_PROXY"
246
+ # Choose N to cancel
247
+ # Expected:
248
+ # - warning lists sessions that reference qa-openai (for example proxy-session / proxy-session-url)
249
+ # - prompt makes clear other proxies share this port
250
+ ```
251
+
252
+ - [ ] `forge proxy delete` prompts for confirmation (not auto-deleted)
253
+ - [ ] Deleting a non-terminal alias lists the related proxy entries sharing that port
254
+ - [ ] Choosing N cancels the delete; alias still in `forge proxy list`
255
+ - [ ] Choosing y deletes the alias while keeping the shared server alive
256
+ - [ ] Deleting the primary QA proxy lists related sessions and same-port aliases
257
+ - [ ] No false warnings about sessions when deleting a non-terminal alias that merely shares the same port
258
+
259
+ ### 4.9 Template Management
260
+
261
+ <!-- auto -->
262
+
263
+ ```bash
264
+ # List available templates
265
+ forge proxy template list
266
+
267
+ # Show a template
268
+ forge proxy template show litellm-openai-local
269
+
270
+ # Raw YAML output (no syntax highlighting)
271
+ forge proxy template show litellm-openai-local --raw
272
+ ```
273
+
274
+ - [ ] `forge proxy template list` shows all templates with source labels (built-in / customized)
275
+ - [ ] `forge proxy template show` displays template YAML
276
+ - [ ] `--raw` outputs plain YAML
277
+
278
+ ### 4.10 Show Raw YAML for a Proxy Instance
279
+
280
+ <!-- prereq: 4.2 -->
281
+
282
+ <!-- auto -->
283
+
284
+ ```bash
285
+ # Show raw YAML for an existing proxy instance (created earlier)
286
+ forge proxy show test-proxy-nostart --raw
287
+ ```
288
+
289
+ - [ ] Proxy instance YAML printed (no syntax highlighting)
290
+ - [ ] YAML includes the expected template/provider fields
291
+
292
+ ### 4.11 Set and Validate Proxy Config (No Editor)
293
+
294
+ <!-- prereq: 4.2 -->
295
+
296
+ <!-- auto -->
297
+
298
+ ```bash
299
+ # Mutate a single value via CLI (no interactive editor)
300
+ forge proxy set test-proxy-nostart default_tier=opus
301
+
302
+ # Validate after mutation
303
+ forge proxy validate test-proxy-nostart
304
+ ```
305
+
306
+ - [ ] `forge proxy set` succeeds
307
+ - [ ] `forge proxy validate` reports config is valid
308
+
309
+ ### 4.12 Stop a Non-Running Proxy (Shared-Port Semantics)
310
+
311
+ <!-- prereq: 4.2 -->
312
+
313
+ <!-- auto -->
314
+
315
+ ```bash
316
+ # test-proxy-nostart shares a port with the running QA OpenAI proxy.
317
+ # Smart-pointer semantics prevent stopping the shared server without --force.
318
+ forge proxy stop test-proxy-nostart 2>&1 || true
319
+
320
+ # Verify: error about shared port, not a silent no-op
321
+ forge proxy stop test-proxy-nostart 2>&1; echo "EXIT=$?"
322
+ ```
323
+
324
+ - [ ] Command refuses to stop: reports other proxies share the port
325
+ - [ ] Exit code is non-zero (shared-port conflict)
326
+
327
+ ### 4.13 Proxy Metrics (Running Proxy)
328
+
329
+ <!-- prereq: 4.2 -->
330
+
331
+ <!-- auto -->
332
+
333
+ ```bash
334
+ # Metrics for a running proxy (QA Gemini proxy created in 4.2)
335
+ forge proxy metrics "$FORGE_QA_GEMINI_PROXY"
336
+
337
+ # JSON output
338
+ forge proxy metrics "$FORGE_QA_GEMINI_PROXY" --json
339
+
340
+ # All proxies
341
+ forge proxy metrics --all
342
+
343
+ # All proxies JSON (must be a single valid JSON object)
344
+ forge proxy metrics --all --json
345
+ ```
346
+
347
+ - [ ] `forge proxy metrics` displays request counts, token totals, per-tier breakdown
348
+ - [ ] Per-tier breakdown includes avg latency
349
+ - [ ] `--json` outputs valid parseable JSON
350
+ - [ ] `--all --json` outputs a single valid JSON object (not one per proxy)
351
+ - [ ] Unreachable proxies show `null` in `--all --json` output
352
+
353
+ ### 4.14 Proxy Metrics (Not Found / Shared-Port)
354
+
355
+ <!-- auto -->
356
+
357
+ ```bash
358
+ # Metrics for a non-existent proxy (not in registry)
359
+ forge proxy metrics nonexistent-proxy 2>&1; echo "EXIT=$?"
360
+
361
+ # Metrics for test-proxy-nostart: shares a port with qa-openai,
362
+ # so smart-pointer semantics mean it reports metrics from the shared server.
363
+ forge proxy metrics test-proxy-nostart
364
+ ```
365
+
366
+ - [ ] Non-existent proxy shows error and exits non-zero
367
+ - [ ] Shared-port proxy (test-proxy-nostart) returns metrics from the shared server (exit 0)
368
+
369
+ ### 4.15 Backend List (Proxy Dependency)
370
+
371
+ <!-- auto -->
372
+
373
+ ```bash
374
+ # List running backend instances (LiteLLM, etc.)
375
+ forge backend list
376
+ ```
377
+
378
+ - [ ] Shows "No backends found." (or lists running backends)
379
+ - [ ] Command suggests `forge backend create litellm` when empty
380
+
381
+ ### 4.16 Backend Create (LiteLLM Config)
382
+
383
+ <!-- auto -->
384
+
385
+ ```bash
386
+ # Create backend config (shared by all instances)
387
+ forge backend create litellm
388
+
389
+ # Show config + status (even if not running)
390
+ forge backend show litellm-4000 --raw
391
+ ```
392
+
393
+ - [ ] Backend config created (or reports it already exists)
394
+ - [ ] `forge backend show` displays config YAML
395
+
396
+ ### 4.17 OpenRouter Templates
397
+
398
+ <!-- auto -->
399
+
400
+ ```bash
401
+ # List all templates -- should now include OpenRouter alongside LiteLLM
402
+ forge proxy template list
403
+
404
+ # Show each OpenRouter template
405
+ forge proxy template show openrouter-anthropic
406
+ forge proxy template show openrouter-openai
407
+ forge proxy template show openrouter-openai-codex
408
+ forge proxy template show openrouter-deepseek
409
+ forge proxy template show openrouter-gemini
410
+ forge proxy template show openrouter-gemini-flash
411
+ forge proxy template show openrouter-glm
412
+ forge proxy template show openrouter-kimi
413
+ forge proxy template show openrouter-minimax
414
+ forge proxy template show openrouter-qwen
415
+ ```
416
+
417
+ - [ ] `forge proxy template list` shows 18 user-facing templates total (8 litellm + 10 openrouter)
418
+ - [ ] `openrouter-anthropic` maps tiers to Claude models (haiku=claude-haiku-4.5, sonnet=claude-sonnet-4.6,
419
+ opus=claude-opus-4.6)
420
+ - [ ] `openrouter-deepseek` maps tiers to DeepSeek models (haiku=deepseek-v4-flash, sonnet/opus=deepseek-v4-pro)
421
+ - [ ] `openrouter-glm` maps tiers to GLM models (haiku=glm-4.7-flash, sonnet/opus=glm-5.1)
422
+ - [ ] `openrouter-kimi` maps tiers to Gemma/Kimi models (haiku=gemma-4-31b-it, sonnet/opus=kimi-k2.6)
423
+ - [ ] `openrouter-minimax` maps tiers to Gemma/MiniMax models (haiku=gemma-4-31b-it, sonnet/opus=minimax-m2.7)
424
+ - [ ] `openrouter-qwen` maps tiers to Qwen models (haiku=qwen3.6-flash, sonnet=qwen3.6-plus, opus=qwen3.6-max-preview)
425
+ - [ ] `openrouter-openai` maps tiers to GPT models (haiku=gpt-5.4-mini, sonnet=gpt-5.5, opus=gpt-5.5)
426
+ - [ ] `openrouter-openai-codex` maps tiers to Codex models (haiku=gpt-5.1-codex-mini, sonnet=gpt-5.3-codex,
427
+ opus=gpt-5.5)
428
+ - [ ] `openrouter-gemini` maps tiers to Gemini models (haiku=gemini-2.5-flash, sonnet=gemini-3.1-pro-preview,
429
+ opus=gemini-3.1-pro-preview)
430
+ - [ ] `openrouter-gemini-flash` maps all tiers to gemini-2.5-flash with tier_overrides for reasoning_effort
431
+ (low/medium/high)
432
+ - [ ] Each OpenRouter template has a distinct default_port (8095-8104)
433
+
434
+ ### 4.18 OpenRouter Proxy Create
435
+
436
+ <!-- auto -->
437
+
438
+ ```bash
439
+ # Clean up from previous runs
440
+ forge proxy delete openrouter-test --force 2>/dev/null || true
441
+
442
+ # Create an OpenRouter proxy without starting it (no OPENROUTER_API_KEY needed for config-only)
443
+ forge proxy create openrouter-anthropic --name openrouter-test --no-start
444
+
445
+ # Show proxy details
446
+ forge proxy show openrouter-test
447
+
448
+ # Show raw YAML
449
+ forge proxy show openrouter-test --raw
450
+ ```
451
+
452
+ - [ ] OpenRouter proxy created from template (exit 0)
453
+ - [ ] `forge proxy show` or `forge proxy validate` displays `Provider: openrouter`
454
+ - [ ] Raw YAML shows `provider: openrouter` and tier mappings with `anthropic/` prefixed model IDs
455
+ - [ ] Proxy uses port 8095 (openrouter-anthropic default)
456
+
457
+ ### 4.19 Model Alternatives
458
+
459
+ <!-- prereq: 4.18 -->
460
+
461
+ <!-- auto -->
462
+
463
+ ```bash
464
+ # Check model_alternatives in the openrouter-anthropic template
465
+ forge proxy template show openrouter-anthropic --raw | grep -A3 model_alternatives
466
+
467
+ # Check instance inherits alternatives
468
+ forge proxy show openrouter-test --raw | grep -A3 model_alternatives
469
+
470
+ echo "---"
471
+
472
+ # Clean up
473
+ forge proxy delete openrouter-test --force 2>/dev/null || true
474
+ ```
475
+
476
+ - [ ] Template YAML includes `model_alternatives` section under opus tier
477
+ - [ ] Opus alternative maps `claude-opus-4-7` to `anthropic/claude-opus-4.7`
478
+ - [ ] Proxy instance inherits `model_alternatives` from template
479
+ - [ ] `openrouter-test` proxy cleaned up
480
+
481
+ ---