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.
- forge/__init__.py +3 -0
- forge/_extensions/agents/.gitkeep +0 -0
- forge/_extensions/commands/.gitkeep +0 -0
- forge/_extensions/skills/analyze/SKILL.md +87 -0
- forge/_extensions/skills/challenge/SKILL.md +91 -0
- forge/_extensions/skills/consensus/SKILL.md +120 -0
- forge/_extensions/skills/consensus/resources/code_consensus_evaluation.md +94 -0
- forge/_extensions/skills/consensus/resources/consensus_evaluation.md +70 -0
- forge/_extensions/skills/consensus/resources/synthesis.md +101 -0
- forge/_extensions/skills/debate/SKILL.md +116 -0
- forge/_extensions/skills/debate/resources/code_debate_evaluation.md +101 -0
- forge/_extensions/skills/debate/resources/debate_evaluation.md +90 -0
- forge/_extensions/skills/panel/SKILL.md +141 -0
- forge/_extensions/skills/panel/resources/synthesis.md +103 -0
- forge/_extensions/skills/qa/SKILL.md +704 -0
- forge/_extensions/skills/qa/resources/checklist/0-enable.md +78 -0
- forge/_extensions/skills/qa/resources/checklist/1-preflight.md +24 -0
- forge/_extensions/skills/qa/resources/checklist/10-resume.md +143 -0
- forge/_extensions/skills/qa/resources/checklist/11-config.md +150 -0
- forge/_extensions/skills/qa/resources/checklist/12-search.md +58 -0
- forge/_extensions/skills/qa/resources/checklist/13-guard.md +237 -0
- forge/_extensions/skills/qa/resources/checklist/14-workflow.md +305 -0
- forge/_extensions/skills/qa/resources/checklist/15-skills.md +155 -0
- forge/_extensions/skills/qa/resources/checklist/16-handoff.md +224 -0
- forge/_extensions/skills/qa/resources/checklist/17-info.md +50 -0
- forge/_extensions/skills/qa/resources/checklist/18-disable.md +84 -0
- forge/_extensions/skills/qa/resources/checklist/19-uninstall.md +146 -0
- forge/_extensions/skills/qa/resources/checklist/2-extensions.md +188 -0
- forge/_extensions/skills/qa/resources/checklist/20-cleanup.md +36 -0
- forge/_extensions/skills/qa/resources/checklist/3-auth.md +234 -0
- forge/_extensions/skills/qa/resources/checklist/4-proxy.md +481 -0
- forge/_extensions/skills/qa/resources/checklist/5-session.md +541 -0
- forge/_extensions/skills/qa/resources/checklist/6-hooks.md +275 -0
- forge/_extensions/skills/qa/resources/checklist/7-costs.md +309 -0
- forge/_extensions/skills/qa/resources/checklist/8-status-line.md +174 -0
- forge/_extensions/skills/qa/resources/checklist/9-direct-commands.md +146 -0
- forge/_extensions/skills/qa/resources/checklist.md +103 -0
- forge/_extensions/skills/qa/resources/report-template.md +62 -0
- forge/_extensions/skills/qa/scripts/start-container.sh +529 -0
- forge/_extensions/skills/qa/scripts/walkthrough-state.py +1137 -0
- forge/_extensions/skills/review/SKILL.md +125 -0
- forge/_extensions/skills/review/references/claude-4.6.md +474 -0
- forge/_extensions/skills/review/references/claude-4.7.md +710 -0
- forge/_extensions/skills/review/references/gemini-3.1.md +546 -0
- forge/_extensions/skills/review/references/gpt-5.5.md +490 -0
- forge/_extensions/skills/review/references/skills-writing-guide.md +1588 -0
- forge/_extensions/skills/review/resources/code-anthropic.md +160 -0
- forge/_extensions/skills/review/resources/code-gemini.md +184 -0
- forge/_extensions/skills/review/resources/code-openai.md +203 -0
- forge/_extensions/skills/review/resources/code.md +160 -0
- forge/_extensions/skills/review-docs/SKILL.md +121 -0
- forge/_extensions/skills/review-docs/resources/docs-anthropic.md +170 -0
- forge/_extensions/skills/review-docs/resources/docs-gemini.md +204 -0
- forge/_extensions/skills/review-docs/resources/docs-openai.md +231 -0
- forge/_extensions/skills/review-docs/resources/docs.md +170 -0
- forge/_extensions/skills/smoke-test/SKILL.md +27 -0
- forge/_extensions/skills/smoke-test/scripts/smoke-test.sh +118 -0
- forge/_extensions/skills/understand/SKILL.md +148 -0
- forge/_extensions/skills/understand/resources/code-anthropic.md +163 -0
- forge/_extensions/skills/understand/resources/code-gemini.md +194 -0
- forge/_extensions/skills/understand/resources/code-openai.md +181 -0
- forge/_extensions/skills/understand/resources/code.md +163 -0
- forge/_extensions/skills/understand/resources/docs-anthropic.md +177 -0
- forge/_extensions/skills/understand/resources/docs-gemini.md +202 -0
- forge/_extensions/skills/understand/resources/docs-openai.md +191 -0
- forge/_extensions/skills/understand/resources/docs.md +177 -0
- forge/_extensions/skills/walkthrough/SKILL.md +599 -0
- forge/_extensions/skills/walkthrough/resources/checklist.md +765 -0
- forge/_extensions/skills/walkthrough/scripts/run-in-repo.sh +118 -0
- forge/_extensions/skills/walkthrough/scripts/setup-test-repo.sh +198 -0
- forge/_extensions/skills/walkthrough/scripts/walkthrough-state.py +1137 -0
- forge/backend/__init__.py +174 -0
- forge/backend/adapters/__init__.py +38 -0
- forge/backend/adapters/litellm.py +158 -0
- forge/backend/creation.py +89 -0
- forge/backend/registry.py +178 -0
- forge/cli/__init__.py +16 -0
- forge/cli/auth.py +483 -0
- forge/cli/backend.py +298 -0
- forge/cli/claude.py +411 -0
- forge/cli/config_cmd.py +303 -0
- forge/cli/extensions.py +1001 -0
- forge/cli/gc.py +165 -0
- forge/cli/guard.py +1018 -0
- forge/cli/guards.py +106 -0
- forge/cli/handoff.py +110 -0
- forge/cli/hooks/__init__.py +36 -0
- forge/cli/hooks/_group.py +20 -0
- forge/cli/hooks/_helpers.py +149 -0
- forge/cli/hooks/commands.py +1677 -0
- forge/cli/hooks/direct_commands.py +1304 -0
- forge/cli/hooks/install.py +232 -0
- forge/cli/hooks/policy.py +151 -0
- forge/cli/hooks/read_hygiene.py +74 -0
- forge/cli/hooks/verification.py +370 -0
- forge/cli/logs.py +406 -0
- forge/cli/main.py +292 -0
- forge/cli/proxy.py +1821 -0
- forge/cli/proxy_costs.py +313 -0
- forge/cli/search.py +416 -0
- forge/cli/session.py +892 -0
- forge/cli/session_addendum.py +81 -0
- forge/cli/session_fork.py +750 -0
- forge/cli/session_handoff.py +141 -0
- forge/cli/session_lifecycle.py +2053 -0
- forge/cli/session_manage.py +1336 -0
- forge/cli/session_memory.py +201 -0
- forge/cli/status_line.py +1398 -0
- forge/cli/workflow.py +1964 -0
- forge/config/__init__.py +110 -0
- forge/config/dataclass_utils.py +88 -0
- forge/config/defaults/__init__.py +0 -0
- forge/config/defaults/backends/__init__.py +0 -0
- forge/config/defaults/backends/litellm.yaml +196 -0
- forge/config/defaults/templates/__init__.py +0 -0
- forge/config/defaults/templates/litellm-anthropic-local.yaml +33 -0
- forge/config/defaults/templates/litellm-anthropic.yaml +24 -0
- forge/config/defaults/templates/litellm-gemini-flash-local.yaml +37 -0
- forge/config/defaults/templates/litellm-gemini-local.yaml +32 -0
- forge/config/defaults/templates/litellm-gemini-test.yaml +34 -0
- forge/config/defaults/templates/litellm-gemini.yaml +21 -0
- forge/config/defaults/templates/litellm-openai-codex-local.yaml +36 -0
- forge/config/defaults/templates/litellm-openai-local.yaml +38 -0
- forge/config/defaults/templates/litellm-openai.yaml +28 -0
- forge/config/defaults/templates/openrouter-anthropic.yaml +23 -0
- forge/config/defaults/templates/openrouter-deepseek.yaml +26 -0
- forge/config/defaults/templates/openrouter-gemini-flash.yaml +26 -0
- forge/config/defaults/templates/openrouter-gemini.yaml +23 -0
- forge/config/defaults/templates/openrouter-glm.yaml +23 -0
- forge/config/defaults/templates/openrouter-kimi.yaml +30 -0
- forge/config/defaults/templates/openrouter-minimax.yaml +26 -0
- forge/config/defaults/templates/openrouter-openai-codex.yaml +23 -0
- forge/config/defaults/templates/openrouter-openai.yaml +28 -0
- forge/config/defaults/templates/openrouter-qwen.yaml +25 -0
- forge/config/loader.py +675 -0
- forge/config/schema.py +448 -0
- forge/core/__init__.py +5 -0
- forge/core/auth/__init__.py +67 -0
- forge/core/auth/capabilities.py +219 -0
- forge/core/auth/credentials_file.py +244 -0
- forge/core/auth/protocols.py +18 -0
- forge/core/auth/secrets.py +243 -0
- forge/core/auth/template_secrets.py +112 -0
- forge/core/data/__init__.py +5 -0
- forge/core/data/model_catalog.yaml +1522 -0
- forge/core/data/pricing.yaml +140 -0
- forge/core/data/system_prompt_addendums/__init__.py +0 -0
- forge/core/data/system_prompt_addendums/gemini.md +330 -0
- forge/core/data/system_prompt_addendums/openai.md +328 -0
- forge/core/llm/__init__.py +231 -0
- forge/core/llm/clients/__init__.py +14 -0
- forge/core/llm/clients/base.py +115 -0
- forge/core/llm/clients/litellm.py +619 -0
- forge/core/llm/clients/openai_compat.py +244 -0
- forge/core/llm/clients/openrouter.py +234 -0
- forge/core/llm/credentials.py +439 -0
- forge/core/llm/detection.py +86 -0
- forge/core/llm/errors.py +44 -0
- forge/core/llm/protocols.py +80 -0
- forge/core/llm/types.py +176 -0
- forge/core/logging.py +146 -0
- forge/core/models/__init__.py +91 -0
- forge/core/models/catalog.py +467 -0
- forge/core/models/pricing.py +165 -0
- forge/core/models/types.py +167 -0
- forge/core/naming.py +212 -0
- forge/core/ops/__init__.py +73 -0
- forge/core/ops/context.py +141 -0
- forge/core/ops/gc.py +802 -0
- forge/core/ops/proxy.py +146 -0
- forge/core/ops/resolution.py +135 -0
- forge/core/ops/session.py +344 -0
- forge/core/ops/session_context.py +548 -0
- forge/core/paths.py +38 -0
- forge/core/process.py +54 -0
- forge/core/reactive/__init__.py +38 -0
- forge/core/reactive/cost_tracking.py +300 -0
- forge/core/reactive/env.py +180 -0
- forge/core/reactive/proxy.py +78 -0
- forge/core/reactive/routing.py +622 -0
- forge/core/reactive/session_runner.py +185 -0
- forge/core/reactive/structured_output.py +62 -0
- forge/core/reactive/tagger.py +94 -0
- forge/core/reactive/throttle.py +132 -0
- forge/core/state/__init__.py +59 -0
- forge/core/state/exceptions.py +59 -0
- forge/core/state/io.py +140 -0
- forge/core/state/lock.py +99 -0
- forge/core/state/timestamps.py +60 -0
- forge/core/transcript.py +78 -0
- forge/core/typing_helpers.py +24 -0
- forge/core/workqueue/__init__.py +67 -0
- forge/core/workqueue/queue.py +552 -0
- forge/core/workqueue/types.py +63 -0
- forge/guard/__init__.py +26 -0
- forge/guard/deterministic/__init__.py +26 -0
- forge/guard/deterministic/base.py +158 -0
- forge/guard/deterministic/coding_standards.py +256 -0
- forge/guard/deterministic/registry.py +148 -0
- forge/guard/deterministic/tdd.py +171 -0
- forge/guard/engine.py +216 -0
- forge/guard/protocols.py +91 -0
- forge/guard/queries.py +96 -0
- forge/guard/semantic/__init__.py +34 -0
- forge/guard/semantic/promotion.py +18 -0
- forge/guard/semantic/supervisor.py +813 -0
- forge/guard/semantic/verdict.py +183 -0
- forge/guard/store.py +124 -0
- forge/guard/team/__init__.py +6 -0
- forge/guard/team/config.py +24 -0
- forge/guard/team/handlers.py +209 -0
- forge/guard/team/prompts.py +41 -0
- forge/guard/types.py +125 -0
- forge/guard/workflow/__init__.py +17 -0
- forge/guard/workflow/branches.py +67 -0
- forge/guard/workflow/config.py +63 -0
- forge/guard/workflow/divergence.py +113 -0
- forge/guard/workflow/policy.py +87 -0
- forge/guard/workflow/stages.py +205 -0
- forge/install/__init__.py +55 -0
- forge/install/cli.py +281 -0
- forge/install/exceptions.py +163 -0
- forge/install/hooks.py +109 -0
- forge/install/installer.py +1037 -0
- forge/install/models.py +321 -0
- forge/install/preset.py +272 -0
- forge/install/settings_merge.py +831 -0
- forge/install/tracking.py +238 -0
- forge/install/version.py +141 -0
- forge/proxy/__init__.py +0 -0
- forge/proxy/base_client.py +181 -0
- forge/proxy/client_adapter.py +476 -0
- forge/proxy/client_factory.py +531 -0
- forge/proxy/converters.py +1206 -0
- forge/proxy/cost_logger.py +132 -0
- forge/proxy/cost_tracker.py +242 -0
- forge/proxy/data_models.py +338 -0
- forge/proxy/error_hints.py +92 -0
- forge/proxy/metrics.py +222 -0
- forge/proxy/model_spec.py +158 -0
- forge/proxy/proxies.py +333 -0
- forge/proxy/proxy_identity.py +134 -0
- forge/proxy/proxy_orchestrator.py +1018 -0
- forge/proxy/proxy_startup.py +54 -0
- forge/proxy/server.py +1561 -0
- forge/proxy/utils.py +537 -0
- forge/review/__init__.py +6 -0
- forge/review/adversarial.py +111 -0
- forge/review/consensus.py +236 -0
- forge/review/engine.py +356 -0
- forge/review/models.py +437 -0
- forge/review/resources/__init__.py +5 -0
- forge/review/resources/codereview-performance.md +85 -0
- forge/review/resources/codereview-quick.md +75 -0
- forge/review/resources/codereview-security.md +92 -0
- forge/review/resources/codereview.md +85 -0
- forge/review/resources/docreview-quick.md +75 -0
- forge/review/resources/docreview.md +86 -0
- forge/review/resources/thinkdeep.md +89 -0
- forge/review/routing.py +368 -0
- forge/review/synthesis.py +73 -0
- forge/runtime_config.py +438 -0
- forge/search/__init__.py +55 -0
- forge/search/bm25_store.py +264 -0
- forge/search/content_store.py +197 -0
- forge/search/engine.py +352 -0
- forge/search/exceptions.py +51 -0
- forge/search/extractor.py +234 -0
- forge/search/index_state.py +295 -0
- forge/search/store.py +215 -0
- forge/search/tokenizer.py +24 -0
- forge/session/__init__.py +130 -0
- forge/session/active.py +339 -0
- forge/session/artifacts.py +202 -0
- forge/session/claude/__init__.py +50 -0
- forge/session/claude/cleanup.py +105 -0
- forge/session/claude/invoke.py +236 -0
- forge/session/claude/paths.py +200 -0
- forge/session/cleanup.py +216 -0
- forge/session/config.py +34 -0
- forge/session/direct_model.py +107 -0
- forge/session/effective.py +169 -0
- forge/session/exceptions.py +255 -0
- forge/session/handoff.py +881 -0
- forge/session/handoff_agent.py +544 -0
- forge/session/hooks/__init__.py +35 -0
- forge/session/hooks/models.py +73 -0
- forge/session/hooks/session_start.py +507 -0
- forge/session/identity.py +84 -0
- forge/session/index.py +553 -0
- forge/session/manager.py +1506 -0
- forge/session/models.py +572 -0
- forge/session/overrides.py +344 -0
- forge/session/plan_resolution.py +286 -0
- forge/session/prev_sessions.py +128 -0
- forge/session/store.py +431 -0
- forge/session/validation.py +47 -0
- forge/session/worktree/__init__.py +65 -0
- forge/session/worktree/cleanup.py +262 -0
- forge/session/worktree/config_copy.py +203 -0
- forge/session/worktree/create.py +332 -0
- forge/sidecar/__init__.py +29 -0
- forge/sidecar/container.py +161 -0
- forge/sidecar/docker.py +86 -0
- forge/sidecar/secrets.py +19 -0
- multi_forge-0.2.0.dist-info/METADATA +242 -0
- multi_forge-0.2.0.dist-info/RECORD +311 -0
- multi_forge-0.2.0.dist-info/WHEEL +4 -0
- multi_forge-0.2.0.dist-info/entry_points.txt +2 -0
- multi_forge-0.2.0.dist-info/licenses/LICENSE +203 -0
- multi_forge-0.2.0.dist-info/licenses/NOTICE +14 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# General Code Review
|
|
2
|
+
|
|
3
|
+
```xml
|
|
4
|
+
<role>
|
|
5
|
+
You are a senior code reviewer performing a thorough analysis.
|
|
6
|
+
You identify bugs, design issues, security concerns, and performance problems.
|
|
7
|
+
You provide actionable feedback with specific code references.
|
|
8
|
+
</role>
|
|
9
|
+
|
|
10
|
+
<behavior>
|
|
11
|
+
- Read all code in scope before forming opinions
|
|
12
|
+
- Cite specific file:line references for every finding
|
|
13
|
+
- Prioritize correctness and security over style
|
|
14
|
+
- Cover ALL files in ONE pass — do not present partial results
|
|
15
|
+
- Be specific: "potential null dereference at auth.py:45" not "might have issues"
|
|
16
|
+
</behavior>
|
|
17
|
+
|
|
18
|
+
<scope_constraints>
|
|
19
|
+
- Review only what's in scope
|
|
20
|
+
- Do not expand to adjacent code unless directly affected
|
|
21
|
+
- If tests exist for reviewed code, check them for coverage gaps
|
|
22
|
+
</scope_constraints>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Review Framework
|
|
28
|
+
|
|
29
|
+
### Quality
|
|
30
|
+
|
|
31
|
+
- Logic errors and edge cases
|
|
32
|
+
- Error handling: are errors caught, propagated, and surfaced correctly?
|
|
33
|
+
- Type safety: do type annotations match runtime behavior?
|
|
34
|
+
- Test coverage: are critical paths tested?
|
|
35
|
+
|
|
36
|
+
### Security
|
|
37
|
+
|
|
38
|
+
- Input validation at trust boundaries
|
|
39
|
+
- Injection vectors (command, SQL, path traversal)
|
|
40
|
+
- Secrets in code or logs
|
|
41
|
+
- Authentication and authorization gaps
|
|
42
|
+
|
|
43
|
+
### Performance
|
|
44
|
+
|
|
45
|
+
- Unnecessary allocations or copies in hot paths
|
|
46
|
+
- N+1 query patterns
|
|
47
|
+
- Missing caching where data is reused
|
|
48
|
+
- Blocking calls in async contexts
|
|
49
|
+
|
|
50
|
+
### Architecture
|
|
51
|
+
|
|
52
|
+
- Component boundaries: is coupling appropriate?
|
|
53
|
+
- Dependency direction: do imports flow the right way?
|
|
54
|
+
- Abstraction level: is complexity in the right place?
|
|
55
|
+
- Interface contracts: are public APIs stable and well-defined?
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Output Format
|
|
60
|
+
|
|
61
|
+
```xml
|
|
62
|
+
<output_format>
|
|
63
|
+
## Summary
|
|
64
|
+
1-2 sentence assessment of overall code quality
|
|
65
|
+
|
|
66
|
+
## Findings
|
|
67
|
+
| Severity | Category | Issue | Location |
|
|
68
|
+
|----------|----------|-------|----------|
|
|
69
|
+
|
|
70
|
+
Severities: CRITICAL > HIGH > MEDIUM > LOW
|
|
71
|
+
|
|
72
|
+
## Recommendations
|
|
73
|
+
Top 3-5 fixes, prioritized by severity and effort
|
|
74
|
+
|
|
75
|
+
## Strengths
|
|
76
|
+
Correct implementations worth preserving
|
|
77
|
+
</output_format>
|
|
78
|
+
|
|
79
|
+
<output_constraints>
|
|
80
|
+
- Each finding: 1-2 sentences with file:line reference
|
|
81
|
+
- Use tables for structured data
|
|
82
|
+
- No verbose narratives or filler
|
|
83
|
+
- Do not restate the review request
|
|
84
|
+
</output_constraints>
|
|
85
|
+
```
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Quick Document Review
|
|
2
|
+
|
|
3
|
+
```xml
|
|
4
|
+
<role>
|
|
5
|
+
You are a senior technical reviewer performing a rapid document assessment.
|
|
6
|
+
You identify only the most important issues -- skip minor concerns.
|
|
7
|
+
You provide actionable feedback with specific section references.
|
|
8
|
+
</role>
|
|
9
|
+
|
|
10
|
+
<behavior>
|
|
11
|
+
- Scan all document content in scope quickly
|
|
12
|
+
- Cite specific section headers or line references for every finding
|
|
13
|
+
- Report only CRITICAL and HIGH severity findings
|
|
14
|
+
- Cover ALL sections in ONE pass -- do not present partial results
|
|
15
|
+
- Be specific and concise: one sentence per finding
|
|
16
|
+
</behavior>
|
|
17
|
+
|
|
18
|
+
<scope_constraints>
|
|
19
|
+
- Review only what's in scope
|
|
20
|
+
- Do not expand to adjacent documents
|
|
21
|
+
- Skip formatting, typos, and minor wording issues
|
|
22
|
+
</scope_constraints>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Review Framework
|
|
28
|
+
|
|
29
|
+
Focus on the most impactful categories only:
|
|
30
|
+
|
|
31
|
+
### Correctness
|
|
32
|
+
|
|
33
|
+
- Contradictions between sections
|
|
34
|
+
- Factually wrong claims or outdated information
|
|
35
|
+
- Undefined references to non-existent sections or documents
|
|
36
|
+
|
|
37
|
+
### Completeness
|
|
38
|
+
|
|
39
|
+
- Critical gaps that would block implementation
|
|
40
|
+
- Promised sections that are missing or empty
|
|
41
|
+
|
|
42
|
+
### Clarity
|
|
43
|
+
|
|
44
|
+
- Sections that are ambiguous enough to cause misimplementation
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Output Format
|
|
49
|
+
|
|
50
|
+
```xml
|
|
51
|
+
<output_format>
|
|
52
|
+
## Summary
|
|
53
|
+
1-2 sentence assessment of overall document quality
|
|
54
|
+
|
|
55
|
+
## Findings
|
|
56
|
+
| Severity | Category | Issue | Section |
|
|
57
|
+
|----------|----------|-------|---------|
|
|
58
|
+
|
|
59
|
+
Severities: CRITICAL > HIGH > MEDIUM > LOW
|
|
60
|
+
Categories: CONTRADICTION, INCOMPLETE, AMBIGUOUS, UNDEFINED_REFERENCE
|
|
61
|
+
|
|
62
|
+
## Recommendations
|
|
63
|
+
Top 3-5 fixes, prioritized by severity and impact
|
|
64
|
+
|
|
65
|
+
## Strengths
|
|
66
|
+
Well-written sections worth preserving as-is
|
|
67
|
+
</output_format>
|
|
68
|
+
|
|
69
|
+
<output_constraints>
|
|
70
|
+
- Each finding: 1-2 sentences with section reference
|
|
71
|
+
- Use tables for structured data
|
|
72
|
+
- No verbose narratives or filler
|
|
73
|
+
- Do not restate the review request
|
|
74
|
+
</output_constraints>
|
|
75
|
+
```
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# General Document Review
|
|
2
|
+
|
|
3
|
+
```xml
|
|
4
|
+
<role>
|
|
5
|
+
You are a senior technical reviewer performing a thorough document analysis.
|
|
6
|
+
You identify contradictions, gaps, ambiguities, and undefined references.
|
|
7
|
+
You provide actionable feedback with specific section references.
|
|
8
|
+
</role>
|
|
9
|
+
|
|
10
|
+
<behavior>
|
|
11
|
+
- Read all document content in scope before forming opinions
|
|
12
|
+
- Cite specific section headers or line references for every finding
|
|
13
|
+
- Prioritize correctness and completeness over formatting
|
|
14
|
+
- Cover ALL sections in ONE pass -- do not present partial results
|
|
15
|
+
- Be specific: "Section 3.2 contradicts Section 5.1 on timeout behavior" not "might have inconsistencies"
|
|
16
|
+
</behavior>
|
|
17
|
+
|
|
18
|
+
<scope_constraints>
|
|
19
|
+
- Review only what's in scope
|
|
20
|
+
- Do not expand to adjacent documents unless directly referenced
|
|
21
|
+
- If the document references external specs or code, note unverifiable claims
|
|
22
|
+
</scope_constraints>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Review Framework
|
|
28
|
+
|
|
29
|
+
### Completeness
|
|
30
|
+
|
|
31
|
+
- Are all stated goals addressed in the body?
|
|
32
|
+
- Are there sections that promise detail but deliver none?
|
|
33
|
+
- Are edge cases, error conditions, and failure modes documented?
|
|
34
|
+
- Are prerequisites and dependencies stated?
|
|
35
|
+
|
|
36
|
+
### Consistency
|
|
37
|
+
|
|
38
|
+
- Do definitions stay consistent across sections?
|
|
39
|
+
- Do examples match the rules they illustrate?
|
|
40
|
+
- Are numeric values, thresholds, and defaults consistent throughout?
|
|
41
|
+
- Do cross-references point to sections that exist and say what's claimed?
|
|
42
|
+
|
|
43
|
+
### Clarity
|
|
44
|
+
|
|
45
|
+
- Can each section be understood without re-reading?
|
|
46
|
+
- Are terms defined before use (or in a glossary)?
|
|
47
|
+
- Are ambiguous pronouns ("it", "this", "the system") resolved?
|
|
48
|
+
- Is the intended audience clear and consistently addressed?
|
|
49
|
+
|
|
50
|
+
### Implementability
|
|
51
|
+
|
|
52
|
+
- Can a developer implement from this document alone?
|
|
53
|
+
- Are interfaces, contracts, and data formats fully specified?
|
|
54
|
+
- Are there unstated assumptions that would block implementation?
|
|
55
|
+
- Are success criteria measurable and testable?
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Output Format
|
|
60
|
+
|
|
61
|
+
```xml
|
|
62
|
+
<output_format>
|
|
63
|
+
## Summary
|
|
64
|
+
1-2 sentence assessment of overall document quality
|
|
65
|
+
|
|
66
|
+
## Findings
|
|
67
|
+
| Severity | Category | Issue | Section |
|
|
68
|
+
|----------|----------|-------|---------|
|
|
69
|
+
|
|
70
|
+
Severities: CRITICAL > HIGH > MEDIUM > LOW
|
|
71
|
+
Categories: CONTRADICTION, INCOMPLETE, AMBIGUOUS, UNDEFINED_REFERENCE
|
|
72
|
+
|
|
73
|
+
## Recommendations
|
|
74
|
+
Top 3-5 fixes, prioritized by severity and impact
|
|
75
|
+
|
|
76
|
+
## Strengths
|
|
77
|
+
Well-written sections worth preserving as-is
|
|
78
|
+
</output_format>
|
|
79
|
+
|
|
80
|
+
<output_constraints>
|
|
81
|
+
- Each finding: 1-2 sentences with section reference
|
|
82
|
+
- Use tables for structured data
|
|
83
|
+
- No verbose narratives or filler
|
|
84
|
+
- Do not restate the review request
|
|
85
|
+
</output_constraints>
|
|
86
|
+
```
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Deep Analysis Framework
|
|
2
|
+
|
|
3
|
+
```xml
|
|
4
|
+
<role>
|
|
5
|
+
You are a senior technical analyst performing deep, structured analysis.
|
|
6
|
+
You decompose complex problems into components, gather evidence,
|
|
7
|
+
evaluate trade-offs, and provide actionable recommendations.
|
|
8
|
+
</role>
|
|
9
|
+
|
|
10
|
+
<behavior>
|
|
11
|
+
- Decompose the problem before analyzing it
|
|
12
|
+
- Support claims with evidence and reasoning
|
|
13
|
+
- Consider multiple perspectives and trade-offs
|
|
14
|
+
- Be direct about unknowns and assumptions
|
|
15
|
+
- Provide concrete, actionable recommendations
|
|
16
|
+
</behavior>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Analysis Process
|
|
22
|
+
|
|
23
|
+
### Phase 1: Decomposition
|
|
24
|
+
|
|
25
|
+
Break the topic into its fundamental components:
|
|
26
|
+
|
|
27
|
+
- What are the key sub-problems or dimensions?
|
|
28
|
+
- What constraints exist?
|
|
29
|
+
- What are the success criteria?
|
|
30
|
+
|
|
31
|
+
### Phase 2: Evidence Gathering
|
|
32
|
+
|
|
33
|
+
For each component, identify:
|
|
34
|
+
|
|
35
|
+
- Relevant code, patterns, or prior art
|
|
36
|
+
- Known constraints (performance, compatibility, complexity)
|
|
37
|
+
- Dependencies and interactions between components
|
|
38
|
+
|
|
39
|
+
### Phase 3: Analysis
|
|
40
|
+
|
|
41
|
+
Evaluate each viable approach:
|
|
42
|
+
|
|
43
|
+
```xml
|
|
44
|
+
<evaluation_criteria>
|
|
45
|
+
- Correctness: Does it solve the actual problem?
|
|
46
|
+
- Simplicity: Is it the minimum viable solution?
|
|
47
|
+
- Maintainability: Can others understand and modify it?
|
|
48
|
+
- Risk: What could go wrong? What's the blast radius?
|
|
49
|
+
</evaluation_criteria>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Phase 4: Recommendations
|
|
53
|
+
|
|
54
|
+
Provide a prioritized list of recommendations:
|
|
55
|
+
|
|
56
|
+
1. **Recommended approach** with rationale
|
|
57
|
+
2. **Alternatives considered** with trade-offs
|
|
58
|
+
3. **Open questions** that need answers before proceeding
|
|
59
|
+
4. **Next steps** in implementation order
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Output Format
|
|
64
|
+
|
|
65
|
+
```xml
|
|
66
|
+
<output_format>
|
|
67
|
+
## Problem Decomposition
|
|
68
|
+
Key components and their relationships
|
|
69
|
+
|
|
70
|
+
## Evidence
|
|
71
|
+
What you found and what it means
|
|
72
|
+
|
|
73
|
+
## Analysis
|
|
74
|
+
Trade-offs, risks, and evaluation of approaches
|
|
75
|
+
|
|
76
|
+
## Recommendations
|
|
77
|
+
Prioritized, actionable recommendations with rationale
|
|
78
|
+
|
|
79
|
+
## Open Questions
|
|
80
|
+
Unresolved items that need further investigation
|
|
81
|
+
</output_format>
|
|
82
|
+
|
|
83
|
+
<output_constraints>
|
|
84
|
+
- Be specific: cite file:line, name exact functions, quote exact errors
|
|
85
|
+
- No hand-waving: "improves performance" is not a claim without evidence
|
|
86
|
+
- Keep recommendations to 3-5 items, prioritized by impact
|
|
87
|
+
- State assumptions explicitly
|
|
88
|
+
</output_constraints>
|
|
89
|
+
```
|
forge/review/routing.py
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""Workflow-specific routing types and functions.
|
|
2
|
+
|
|
3
|
+
Builds on the shared primitives in ``forge.core.reactive.routing``
|
|
4
|
+
to add workflow-specific types (``WorkerRoutingPlan``) and functions
|
|
5
|
+
(``derive_model_routes``, ``resolve_invocation_routing``,
|
|
6
|
+
``resolve_model_flag``).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from collections.abc import Sequence
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import Any, Protocol, runtime_checkable
|
|
15
|
+
|
|
16
|
+
from forge.core.reactive.routing import ModelRoute, RoutingResult
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@runtime_checkable
|
|
20
|
+
class RoutableSpec(Protocol):
|
|
21
|
+
"""Structural protocol for model specs that can be routed.
|
|
22
|
+
|
|
23
|
+
Decouples routing from the concrete ``ModelSpec`` dataclass so
|
|
24
|
+
Phase 2 is type-check clean before Phase 3 adds these fields.
|
|
25
|
+
Once ``ModelSpec`` gains these fields, it satisfies this protocol
|
|
26
|
+
implicitly (structural subtyping).
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def name(self) -> str: ...
|
|
31
|
+
@property
|
|
32
|
+
def family(self) -> str: ...
|
|
33
|
+
@property
|
|
34
|
+
def provider_refs(self) -> tuple[tuple[str, str], ...]: ...
|
|
35
|
+
@property
|
|
36
|
+
def preferred_proxy(self) -> str | None: ...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
_log = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
# Direct workers run claude -p --bare which needs ANTHROPIC_API_KEY
|
|
42
|
+
_DIRECT_CREDENTIAL = "anthropic-api"
|
|
43
|
+
|
|
44
|
+
# Providers that pass model IDs through to the upstream (OpenRouter routes
|
|
45
|
+
# by explicit model ref regardless of the template's native family).
|
|
46
|
+
_PASSTHROUGH_PROVIDERS = frozenset({"openrouter"})
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class WorkerRoutingPlan:
|
|
51
|
+
"""Pre-resolved routing for all workers in a workflow invocation.
|
|
52
|
+
|
|
53
|
+
Created once at invocation start. Frozen and passed to each worker.
|
|
54
|
+
``routes`` is indexed by worker position (same order as spec list).
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
routes: tuple[RoutingResult, ...]
|
|
58
|
+
resolved_at: str
|
|
59
|
+
via_override: str | None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def resolve_model_flag(route: ModelRoute) -> str | None:
|
|
63
|
+
"""Return the ``--model`` flag for a routed workflow worker.
|
|
64
|
+
|
|
65
|
+
Direct workers use Claude Code env pins instead of ``--model``.
|
|
66
|
+
Proxied workers always send an explicit model ref so ``--models``
|
|
67
|
+
means the same thing regardless of which compatible proxy was selected.
|
|
68
|
+
"""
|
|
69
|
+
if route.provider == "direct":
|
|
70
|
+
return None
|
|
71
|
+
return route.model_ref
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ── Template metadata cache ──────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass(frozen=True)
|
|
78
|
+
class _TemplateMeta:
|
|
79
|
+
"""Cached static metadata for one proxy template."""
|
|
80
|
+
|
|
81
|
+
name: str
|
|
82
|
+
family: str
|
|
83
|
+
preferred_provider: str
|
|
84
|
+
credentials: tuple[str, ...]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
_template_cache: dict[str, _TemplateMeta] = {}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _get_template_meta(template_name: str) -> _TemplateMeta | None:
|
|
91
|
+
"""Load and cache template metadata (family, provider, credentials)."""
|
|
92
|
+
if template_name in _template_cache:
|
|
93
|
+
return _template_cache[template_name]
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
import yaml
|
|
97
|
+
|
|
98
|
+
from forge.config.loader import read_template
|
|
99
|
+
|
|
100
|
+
content = read_template(template_name)
|
|
101
|
+
data = yaml.safe_load(content)
|
|
102
|
+
if not isinstance(data, dict):
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
proxy_block = data.get("proxy", {})
|
|
106
|
+
if not isinstance(proxy_block, dict):
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
family = proxy_block.get("family", "")
|
|
110
|
+
provider = proxy_block.get("preferred_provider", "")
|
|
111
|
+
if not family or not provider:
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
from forge.core.auth.capabilities import credentials_for_template
|
|
115
|
+
|
|
116
|
+
creds = credentials_for_template(template_name)
|
|
117
|
+
cred_names = tuple(c.name for c in creds)
|
|
118
|
+
|
|
119
|
+
meta = _TemplateMeta(
|
|
120
|
+
name=template_name,
|
|
121
|
+
family=family,
|
|
122
|
+
preferred_provider=provider,
|
|
123
|
+
credentials=cred_names,
|
|
124
|
+
)
|
|
125
|
+
_template_cache[template_name] = meta
|
|
126
|
+
return meta
|
|
127
|
+
except Exception:
|
|
128
|
+
_log.debug("Could not load metadata for template '%s'", template_name, exc_info=True)
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _all_template_metas() -> list[_TemplateMeta]:
|
|
133
|
+
"""Load metadata for all available templates."""
|
|
134
|
+
from forge.config.loader import list_template_names
|
|
135
|
+
|
|
136
|
+
result: list[_TemplateMeta] = []
|
|
137
|
+
for name in list_template_names(include_internal=False):
|
|
138
|
+
meta = _get_template_meta(name)
|
|
139
|
+
if meta:
|
|
140
|
+
result.append(meta)
|
|
141
|
+
return result
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def clear_template_cache() -> None:
|
|
145
|
+
"""Clear the template metadata cache (for testing)."""
|
|
146
|
+
_template_cache.clear()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# ── Route derivation ─────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def derive_model_routes(spec: RoutableSpec) -> tuple[ModelRoute, ...]:
|
|
153
|
+
"""Expand compact model metadata into concrete routing options.
|
|
154
|
+
|
|
155
|
+
For each provider ref on the model, inspect known proxy templates
|
|
156
|
+
and credential metadata to build route records. Does not inspect
|
|
157
|
+
the proxy registry or check running state.
|
|
158
|
+
|
|
159
|
+
Ranking (deterministic):
|
|
160
|
+
1. preferred_proxy match first (if it matches a derived route)
|
|
161
|
+
2. provider_refs order
|
|
162
|
+
3. Native-family templates before cross-family passthrough
|
|
163
|
+
4. Alphabetical template name tiebreaker
|
|
164
|
+
"""
|
|
165
|
+
all_metas = _all_template_metas()
|
|
166
|
+
routes: list[ModelRoute] = []
|
|
167
|
+
preferred_routes: list[ModelRoute] = []
|
|
168
|
+
|
|
169
|
+
for provider_ns, model_ref in spec.provider_refs:
|
|
170
|
+
if provider_ns == "direct":
|
|
171
|
+
route = ModelRoute(
|
|
172
|
+
provider="direct",
|
|
173
|
+
credential=_DIRECT_CREDENTIAL,
|
|
174
|
+
family=spec.family,
|
|
175
|
+
template_id=None,
|
|
176
|
+
template_family=None,
|
|
177
|
+
model_ref=model_ref,
|
|
178
|
+
)
|
|
179
|
+
if spec.preferred_proxy is None:
|
|
180
|
+
preferred_routes.append(route)
|
|
181
|
+
else:
|
|
182
|
+
routes.append(route)
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
matching = _find_matching_templates(provider_ns, spec.family, all_metas)
|
|
186
|
+
|
|
187
|
+
for meta in matching:
|
|
188
|
+
cred = meta.credentials[0] if meta.credentials else provider_ns
|
|
189
|
+
route = ModelRoute(
|
|
190
|
+
provider=provider_ns,
|
|
191
|
+
credential=cred,
|
|
192
|
+
family=spec.family,
|
|
193
|
+
template_id=meta.name,
|
|
194
|
+
template_family=meta.family,
|
|
195
|
+
model_ref=model_ref,
|
|
196
|
+
)
|
|
197
|
+
if spec.preferred_proxy and meta.name == spec.preferred_proxy:
|
|
198
|
+
preferred_routes.append(route)
|
|
199
|
+
else:
|
|
200
|
+
routes.append(route)
|
|
201
|
+
|
|
202
|
+
return tuple(preferred_routes + routes)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _find_matching_templates(
|
|
206
|
+
provider_ns: str,
|
|
207
|
+
model_family: str,
|
|
208
|
+
all_metas: list[_TemplateMeta],
|
|
209
|
+
) -> list[_TemplateMeta]:
|
|
210
|
+
"""Find templates compatible with a provider namespace.
|
|
211
|
+
|
|
212
|
+
Returns native-family templates first, then cross-family passthrough
|
|
213
|
+
templates (for passthrough providers like OpenRouter), sorted
|
|
214
|
+
alphabetically within each group.
|
|
215
|
+
"""
|
|
216
|
+
native: list[_TemplateMeta] = []
|
|
217
|
+
cross_family: list[_TemplateMeta] = []
|
|
218
|
+
|
|
219
|
+
for meta in all_metas:
|
|
220
|
+
if meta.preferred_provider != provider_ns:
|
|
221
|
+
continue
|
|
222
|
+
|
|
223
|
+
if meta.family == model_family:
|
|
224
|
+
native.append(meta)
|
|
225
|
+
elif provider_ns in _PASSTHROUGH_PROVIDERS:
|
|
226
|
+
cross_family.append(meta)
|
|
227
|
+
|
|
228
|
+
native.sort(key=lambda m: m.name)
|
|
229
|
+
cross_family.sort(key=lambda m: m.name)
|
|
230
|
+
return native + cross_family
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
# ── Invocation routing ───────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def resolve_invocation_routing(
|
|
237
|
+
specs: Sequence[Any],
|
|
238
|
+
via: str | None = None,
|
|
239
|
+
) -> WorkerRoutingPlan:
|
|
240
|
+
"""Resolve routing for all workers at invocation start.
|
|
241
|
+
|
|
242
|
+
Called once by the workflow CLI command. Fail-closed: raises if
|
|
243
|
+
any worker has no route.
|
|
244
|
+
"""
|
|
245
|
+
from forge.core.reactive.routing import resolve_subprocess_routing
|
|
246
|
+
from forge.core.state import now_iso
|
|
247
|
+
|
|
248
|
+
results: list[RoutingResult] = []
|
|
249
|
+
|
|
250
|
+
for spec in specs:
|
|
251
|
+
routes = derive_model_routes(spec)
|
|
252
|
+
|
|
253
|
+
direct_only = bool(routes) and all(r.provider == "direct" for r in routes)
|
|
254
|
+
if direct_only:
|
|
255
|
+
result = _resolve_direct_spec(spec, routes, via)
|
|
256
|
+
else:
|
|
257
|
+
result = resolve_subprocess_routing(
|
|
258
|
+
explicit_proxy=via,
|
|
259
|
+
preferred_proxy=spec.preferred_proxy,
|
|
260
|
+
routes=routes,
|
|
261
|
+
require_route=True,
|
|
262
|
+
advisory_check=True,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if result.route is None:
|
|
266
|
+
_raise_no_route_error(spec, routes)
|
|
267
|
+
|
|
268
|
+
_log_routing_decision(spec, result)
|
|
269
|
+
results.append(result)
|
|
270
|
+
|
|
271
|
+
return WorkerRoutingPlan(
|
|
272
|
+
routes=tuple(results),
|
|
273
|
+
resolved_at=now_iso(),
|
|
274
|
+
via_override=via,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _log_routing_decision(spec: RoutableSpec, result: RoutingResult) -> None:
|
|
279
|
+
"""Emit one consolidated line for workflow routing observability."""
|
|
280
|
+
route = result.route
|
|
281
|
+
model_ref = route.model_ref if route else "(none)"
|
|
282
|
+
template = result.template or (route.template_id if route else None) or "(direct)"
|
|
283
|
+
proxy = result.proxy_id or "(direct)"
|
|
284
|
+
_log.info(
|
|
285
|
+
"Routing decision: model=%s source=%s proxy=%s template=%s model_ref=%s",
|
|
286
|
+
spec.name,
|
|
287
|
+
result.source,
|
|
288
|
+
proxy,
|
|
289
|
+
template,
|
|
290
|
+
model_ref,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _resolve_direct_spec(
|
|
295
|
+
spec: RoutableSpec,
|
|
296
|
+
routes: tuple[ModelRoute, ...],
|
|
297
|
+
via: str | None,
|
|
298
|
+
) -> RoutingResult:
|
|
299
|
+
"""Build a RoutingResult for a direct-only spec, bypassing the resolver."""
|
|
300
|
+
direct_route = next((r for r in routes if r.provider == "direct"), None)
|
|
301
|
+
if direct_route is None:
|
|
302
|
+
_raise_no_route_error(spec, routes)
|
|
303
|
+
|
|
304
|
+
warning = None
|
|
305
|
+
if via:
|
|
306
|
+
warning = f"Worker '{spec.name}' uses direct Anthropic routing; --proxy ignored."
|
|
307
|
+
|
|
308
|
+
return RoutingResult(
|
|
309
|
+
base_url=None,
|
|
310
|
+
proxy_id=None,
|
|
311
|
+
template=None,
|
|
312
|
+
source="direct",
|
|
313
|
+
route=direct_route,
|
|
314
|
+
credential=_DIRECT_CREDENTIAL,
|
|
315
|
+
warning=warning,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _raise_no_route_error(spec: RoutableSpec, routes: tuple[ModelRoute, ...]) -> None:
|
|
320
|
+
"""Raise an actionable error when no route resolves for a workflow worker.
|
|
321
|
+
|
|
322
|
+
Distinguishes "missing credential" from "credential configured but no
|
|
323
|
+
proxy running" to avoid sending the user to forge auth login when they
|
|
324
|
+
need forge proxy create/start instead.
|
|
325
|
+
"""
|
|
326
|
+
try:
|
|
327
|
+
from forge.core.auth.capabilities import (
|
|
328
|
+
CREDENTIALS,
|
|
329
|
+
format_missing_credential_error,
|
|
330
|
+
)
|
|
331
|
+
from forge.core.auth.template_secrets import resolve_env_or_credential
|
|
332
|
+
|
|
333
|
+
if not routes:
|
|
334
|
+
raise RuntimeError(
|
|
335
|
+
f"No routes derived for model '{spec.name}' (family={spec.family}).\n"
|
|
336
|
+
f"Tip: Run 'forge proxy create <template>' for a compatible proxy,\n"
|
|
337
|
+
f" or 'forge auth login' to configure credentials."
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
cred_name = routes[0].credential
|
|
341
|
+
cred = CREDENTIALS.get(cred_name)
|
|
342
|
+
|
|
343
|
+
if cred:
|
|
344
|
+
missing_vars = [ev.name for ev in cred.env_vars if ev.required and not resolve_env_or_credential(ev.name)]
|
|
345
|
+
if missing_vars:
|
|
346
|
+
raise RuntimeError(
|
|
347
|
+
format_missing_credential_error(
|
|
348
|
+
cred,
|
|
349
|
+
missing_vars=missing_vars,
|
|
350
|
+
context=f"Workflow model '{spec.name}'",
|
|
351
|
+
)
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
template_ids = [r.template_id for r in routes if r.template_id]
|
|
355
|
+
if template_ids:
|
|
356
|
+
templates_str = ", ".join(template_ids[:3])
|
|
357
|
+
raise RuntimeError(
|
|
358
|
+
f"No running proxy found for model '{spec.name}'.\n"
|
|
359
|
+
f" Compatible templates: {templates_str}\n"
|
|
360
|
+
f" Tip: Run 'forge proxy create {template_ids[0]}' to create one,\n"
|
|
361
|
+
f" or 'forge proxy start <id>' if one exists."
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
raise RuntimeError(f"No route found for model '{spec.name}' (family={spec.family}).")
|
|
365
|
+
except RuntimeError:
|
|
366
|
+
raise
|
|
367
|
+
except Exception:
|
|
368
|
+
raise RuntimeError(f"No route found for model '{spec.name}' (family={spec.family}).")
|