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,73 @@
|
|
|
1
|
+
"""Output formatting for multi-model review results.
|
|
2
|
+
|
|
3
|
+
Provides two output modes:
|
|
4
|
+
- Synthesis prompt: human-readable text for agent consumption
|
|
5
|
+
- JSON output: structured data for skill/script consumption
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from .models import MultiReviewOutput, ReviewResult
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def format_synthesis_prompt(output: MultiReviewOutput) -> str:
|
|
17
|
+
"""Format review results into a synthesis prompt.
|
|
18
|
+
|
|
19
|
+
Intended for the calling agent to read and synthesize.
|
|
20
|
+
"""
|
|
21
|
+
sections: list[str] = []
|
|
22
|
+
|
|
23
|
+
prompt_preview = output.prompt[:500]
|
|
24
|
+
if len(output.prompt) > 500:
|
|
25
|
+
prompt_preview += "..."
|
|
26
|
+
sections.append(f"I asked {len(output.results)} models the same prompt:\n\n> {prompt_preview}")
|
|
27
|
+
|
|
28
|
+
for result in output.results:
|
|
29
|
+
sections.append(f"\n\n-----\n\n## {result.model_name}'s answer:\n")
|
|
30
|
+
if result.success:
|
|
31
|
+
sections.append(result.stdout)
|
|
32
|
+
else:
|
|
33
|
+
sections.append(f"**Error:** {result.error}")
|
|
34
|
+
|
|
35
|
+
sections.append("""
|
|
36
|
+
|
|
37
|
+
-----
|
|
38
|
+
|
|
39
|
+
## Synthesis Request
|
|
40
|
+
|
|
41
|
+
Now that you've seen all responses:
|
|
42
|
+
|
|
43
|
+
1. **Points you missed**: Any points covered by other models that you did not cover?
|
|
44
|
+
2. **Accuracy check**: Can you verify if they are accurate? Anything you disagree with?
|
|
45
|
+
3. **Overall synthesis**: Provide a unified synthesis combining the best insights from all models.
|
|
46
|
+
""")
|
|
47
|
+
|
|
48
|
+
return "".join(sections)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def format_json_output(output: MultiReviewOutput) -> str:
|
|
52
|
+
"""Format review results as structured JSON."""
|
|
53
|
+
data = build_json_dict(output)
|
|
54
|
+
return json.dumps(data, indent=2)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def build_json_dict(output: MultiReviewOutput) -> dict[str, Any]:
|
|
58
|
+
"""Build the JSON-serializable dict for output."""
|
|
59
|
+
return {
|
|
60
|
+
"prompt": output.prompt,
|
|
61
|
+
"results": {r.model_name: _result_to_dict(r) for r in output.results},
|
|
62
|
+
"successful": output.successful,
|
|
63
|
+
"failed": output.failed,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _result_to_dict(result: ReviewResult) -> dict[str, Any]:
|
|
68
|
+
return {
|
|
69
|
+
"response": result.stdout if result.success else None,
|
|
70
|
+
"error": result.error,
|
|
71
|
+
"duration_seconds": round(result.duration_seconds, 2),
|
|
72
|
+
"success": result.success,
|
|
73
|
+
}
|
forge/runtime_config.py
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
"""Forge runtime configuration (~/.forge/config.yaml).
|
|
2
|
+
|
|
3
|
+
Separate from forge.config (which the proxy imports) to avoid leaking
|
|
4
|
+
runtime preferences into routing. The proxy singleton must never see
|
|
5
|
+
these values — they control CLI/session behavior only.
|
|
6
|
+
|
|
7
|
+
File: ~/.forge/config.yaml (optional, fail-open if missing or invalid).
|
|
8
|
+
|
|
9
|
+
Three-layer resolution (highest precedence wins):
|
|
10
|
+
1. Built-in defaults (dataclass field defaults)
|
|
11
|
+
2. ~/.forge/config.yaml
|
|
12
|
+
3. Environment variables (via _ENV_OVERRIDES mapping)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
import os
|
|
19
|
+
import tempfile
|
|
20
|
+
from dataclasses import asdict, dataclass, fields
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from forge.core.paths import get_forge_home
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
CONFIG_FILENAME = "config.yaml"
|
|
29
|
+
|
|
30
|
+
# Env var → field name mapping. Env vars override YAML values when present.
|
|
31
|
+
# This is the single source of truth for env-to-config overrides.
|
|
32
|
+
_ENV_OVERRIDES: dict[str, str] = {
|
|
33
|
+
"FORGE_DEBUG": "log_level",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class RuntimeConfig:
|
|
39
|
+
"""Global Forge runtime preferences — always reflects effective values.
|
|
40
|
+
|
|
41
|
+
Three-layer resolution: built-in defaults → config.yaml → env vars.
|
|
42
|
+
After loading, all fields represent the effective runtime state.
|
|
43
|
+
They do NOT affect proxy routing (that's ForgeConfig's domain).
|
|
44
|
+
|
|
45
|
+
All fields have sensible defaults — the config file is optional.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
# Proxy execution mode: "host" runs proxy on host, "sidecar" bundles in Docker
|
|
49
|
+
proxy_mode: str = "host"
|
|
50
|
+
|
|
51
|
+
sidecar_image: str = "forge-sidecar:latest"
|
|
52
|
+
|
|
53
|
+
# Version string sent in the User-Agent header to upstream LLM providers.
|
|
54
|
+
user_agent_claude_code_version: str = ""
|
|
55
|
+
|
|
56
|
+
# Optional model override for direct (non-proxy) sessions.
|
|
57
|
+
# Passed to Claude Code via ANTHROPIC_MODEL + ANTHROPIC_DEFAULT_*_MODEL.
|
|
58
|
+
# Empty string = let Claude Code decide.
|
|
59
|
+
default_direct_model: str = ""
|
|
60
|
+
|
|
61
|
+
# Fallback auto-compact window for proxy mode when model lookup fails.
|
|
62
|
+
# Passed as CLAUDE_CODE_AUTO_COMPACT_WINDOW to Claude Code.
|
|
63
|
+
# Direct sessions don't use this — Claude Code handles its own context.
|
|
64
|
+
context_limit: int = 200000
|
|
65
|
+
|
|
66
|
+
# Status line timeout for proxy/git subprocess calls (seconds)
|
|
67
|
+
status_timeout: float = 2.0
|
|
68
|
+
|
|
69
|
+
# Handoff agent default timeout (seconds)
|
|
70
|
+
handoff_timeout: int = 300
|
|
71
|
+
|
|
72
|
+
# File logging level: "off" (no file logging), "debug", "info", "warning"
|
|
73
|
+
# Override: FORGE_DEBUG env var (1/true/yes → "debug", 0/false/no/off → "off")
|
|
74
|
+
log_level: str = "off"
|
|
75
|
+
|
|
76
|
+
# Show Claude.ai rate limit usage in status line (direct sessions only).
|
|
77
|
+
# Off by default — not relevant for enterprise plans.
|
|
78
|
+
show_rate_limits: bool = False
|
|
79
|
+
|
|
80
|
+
# Auto-delete log files older than N days on CLI startup.
|
|
81
|
+
# 0 = disabled (no auto-cleanup). Positive integer = retention window in days.
|
|
82
|
+
log_retention_days: int = 0
|
|
83
|
+
|
|
84
|
+
# Auto-delete sessions older than N days on CLI startup.
|
|
85
|
+
# 0 = disabled (no auto-cleanup). Positive integer = retention window in days.
|
|
86
|
+
# Keeps worktrees and branches; removes manifests, index entries, and Claude
|
|
87
|
+
# transcripts (*.jsonl in ~/.claude/projects/). Forge artifact snapshots
|
|
88
|
+
# under .forge/artifacts/ are NOT removed.
|
|
89
|
+
session_retention_days: int = 0
|
|
90
|
+
|
|
91
|
+
# Policy summary feedback after evaluations: "on" (default), "off".
|
|
92
|
+
# Gates post-hoc "[forge] Policy: checked ..." summary lines and additionalContext.
|
|
93
|
+
# Does NOT affect deny output or substantive warning lines -- those stay visible always.
|
|
94
|
+
policy_summary_feedback: str = "on"
|
|
95
|
+
|
|
96
|
+
# Log tool failures to ~/.forge/logs/tool_failures/ even without debug mode.
|
|
97
|
+
# Off by default because records may include tool inputs and error payloads.
|
|
98
|
+
log_tool_failures: bool = False
|
|
99
|
+
|
|
100
|
+
# Ignore environment variables for credential resolution.
|
|
101
|
+
# When true, Forge reads credentials only from ~/.forge/credentials.yaml,
|
|
102
|
+
# ignoring shell env vars (ANTHROPIC_API_KEY, OPENROUTER_API_KEY, etc.).
|
|
103
|
+
# Useful when shell API keys are for Claude Code (not Forge subprocesses).
|
|
104
|
+
auth_ignore_env: bool = False
|
|
105
|
+
|
|
106
|
+
def __post_init__(self) -> None:
|
|
107
|
+
valid_modes = {"host", "sidecar"}
|
|
108
|
+
if self.proxy_mode not in valid_modes:
|
|
109
|
+
raise ValueError(
|
|
110
|
+
f"Invalid proxy_mode: '{self.proxy_mode}' " f"(must be one of: {', '.join(sorted(valid_modes))})"
|
|
111
|
+
)
|
|
112
|
+
if self.context_limit < 1:
|
|
113
|
+
raise ValueError(f"context_limit must be >= 1, got {self.context_limit}")
|
|
114
|
+
if self.status_timeout <= 0:
|
|
115
|
+
raise ValueError(f"status_timeout must be > 0, got {self.status_timeout}")
|
|
116
|
+
if self.handoff_timeout < 1:
|
|
117
|
+
raise ValueError(f"handoff_timeout must be >= 1, got {self.handoff_timeout}")
|
|
118
|
+
valid_log_levels = {"off", "debug", "info", "warning"}
|
|
119
|
+
if self.log_level not in valid_log_levels:
|
|
120
|
+
raise ValueError(
|
|
121
|
+
f"Invalid log_level: '{self.log_level}' (must be one of: {', '.join(sorted(valid_log_levels))})"
|
|
122
|
+
)
|
|
123
|
+
if self.log_retention_days < 0:
|
|
124
|
+
raise ValueError(f"log_retention_days must be >= 0, got {self.log_retention_days}")
|
|
125
|
+
if self.session_retention_days < 0:
|
|
126
|
+
raise ValueError(f"session_retention_days must be >= 0, got {self.session_retention_days}")
|
|
127
|
+
valid_feedback = {"on", "off"}
|
|
128
|
+
if self.policy_summary_feedback not in valid_feedback:
|
|
129
|
+
raise ValueError(
|
|
130
|
+
f"Invalid policy_summary_feedback: '{self.policy_summary_feedback}' "
|
|
131
|
+
f"(must be one of: {', '.join(sorted(valid_feedback))})"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _coerce_debug_to_log_level(raw: str) -> str:
|
|
136
|
+
"""Coerce FORGE_DEBUG env var to a log_level string."""
|
|
137
|
+
low = raw.lower()
|
|
138
|
+
if low in ("1", "true", "yes"):
|
|
139
|
+
return "debug"
|
|
140
|
+
if low in ("0", "false", "no", "off"):
|
|
141
|
+
return "off"
|
|
142
|
+
if low in ("debug", "info", "warning"):
|
|
143
|
+
return low
|
|
144
|
+
raise ValueError(f"Cannot coerce FORGE_DEBUG={raw!r} to log level")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _coerce_env_value(raw: str, field_info: Any) -> Any:
|
|
148
|
+
"""Coerce a raw env var string to the field's expected Python type."""
|
|
149
|
+
ftype = field_info.type
|
|
150
|
+
if ftype is int or ftype == "int":
|
|
151
|
+
val = int(raw)
|
|
152
|
+
if val < 1:
|
|
153
|
+
raise ValueError(f"must be >= 1, got {val}")
|
|
154
|
+
return val
|
|
155
|
+
if ftype is float or ftype == "float":
|
|
156
|
+
return float(raw)
|
|
157
|
+
if ftype is bool or ftype == "bool":
|
|
158
|
+
if raw.lower() in ("1", "true", "yes"):
|
|
159
|
+
return True
|
|
160
|
+
if raw.lower() in ("0", "false", "no"):
|
|
161
|
+
return False
|
|
162
|
+
raise ValueError(f"Cannot coerce {raw!r} to bool")
|
|
163
|
+
return raw
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _apply_env_overrides(config: RuntimeConfig) -> RuntimeConfig:
|
|
167
|
+
"""Apply environment variable overrides to config values.
|
|
168
|
+
|
|
169
|
+
Per-field: each env var is applied independently. If one parse fails,
|
|
170
|
+
others still apply (fail-open per field, not all-or-nothing).
|
|
171
|
+
Attaches _env_sources dict for display annotation by %config.
|
|
172
|
+
"""
|
|
173
|
+
field_map = {f.name: f for f in fields(RuntimeConfig)}
|
|
174
|
+
overrides: dict[str, Any] = {}
|
|
175
|
+
env_sources: dict[str, str] = {}
|
|
176
|
+
|
|
177
|
+
for env_var, field_name in _ENV_OVERRIDES.items():
|
|
178
|
+
raw = os.environ.get(env_var, "").strip()
|
|
179
|
+
if not raw:
|
|
180
|
+
continue
|
|
181
|
+
try:
|
|
182
|
+
if field_name == "log_level":
|
|
183
|
+
coerced = _coerce_debug_to_log_level(raw)
|
|
184
|
+
else:
|
|
185
|
+
coerced = _coerce_env_value(raw, field_map[field_name])
|
|
186
|
+
overrides[field_name] = coerced
|
|
187
|
+
env_sources[field_name] = env_var
|
|
188
|
+
except (ValueError, TypeError) as e:
|
|
189
|
+
logger.warning("Ignoring env %s=%r: %s", env_var, raw, e)
|
|
190
|
+
|
|
191
|
+
if not overrides:
|
|
192
|
+
object.__setattr__(config, "_env_sources", {})
|
|
193
|
+
return config
|
|
194
|
+
|
|
195
|
+
merged = asdict(config)
|
|
196
|
+
merged.update(overrides)
|
|
197
|
+
try:
|
|
198
|
+
result = RuntimeConfig(**merged)
|
|
199
|
+
except (ValueError, TypeError) as e:
|
|
200
|
+
logger.warning("Env override produced invalid config: %s — ignoring overrides", e)
|
|
201
|
+
object.__setattr__(config, "_env_sources", {})
|
|
202
|
+
return config
|
|
203
|
+
|
|
204
|
+
object.__setattr__(result, "_env_sources", env_sources)
|
|
205
|
+
return result
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# Singleton cache (must be after RuntimeConfig definition)
|
|
209
|
+
_config: RuntimeConfig | None = None
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def get_config_path() -> Path:
|
|
213
|
+
"""Get the path to ~/.forge/config.yaml."""
|
|
214
|
+
return get_forge_home() / CONFIG_FILENAME
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def ensure_config() -> Path:
|
|
218
|
+
"""Ensure the config file exists, creating with defaults if missing.
|
|
219
|
+
|
|
220
|
+
Returns the path to the config file. Idempotent — existing files
|
|
221
|
+
are never overwritten.
|
|
222
|
+
"""
|
|
223
|
+
config_path = get_config_path()
|
|
224
|
+
if not config_path.is_file():
|
|
225
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
226
|
+
config_path.write_text(get_default_config_content())
|
|
227
|
+
os.chmod(str(config_path), 0o600)
|
|
228
|
+
return config_path
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def load_runtime_config(path: Path | None = None) -> RuntimeConfig:
|
|
232
|
+
"""Load runtime config from YAML file, then apply env var overrides.
|
|
233
|
+
|
|
234
|
+
Three-layer resolution: built-in defaults → config.yaml → env vars.
|
|
235
|
+
Fail-open: returns defaults if file is missing, unreadable, or invalid YAML.
|
|
236
|
+
Unknown keys are warned and ignored (forward compatibility).
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
path: Override config file path (for testing). Defaults to ~/.forge/config.yaml.
|
|
240
|
+
"""
|
|
241
|
+
config_path = path or get_config_path()
|
|
242
|
+
|
|
243
|
+
if not config_path.is_file():
|
|
244
|
+
return _apply_env_overrides(RuntimeConfig())
|
|
245
|
+
|
|
246
|
+
try:
|
|
247
|
+
import yaml
|
|
248
|
+
|
|
249
|
+
raw = config_path.read_text(encoding="utf-8")
|
|
250
|
+
data = yaml.safe_load(raw)
|
|
251
|
+
except Exception as e:
|
|
252
|
+
logger.warning("Failed to read %s: %s — using defaults", config_path, e)
|
|
253
|
+
return _apply_env_overrides(RuntimeConfig())
|
|
254
|
+
|
|
255
|
+
if not isinstance(data, dict):
|
|
256
|
+
logger.warning("%s is not a YAML mapping — using defaults", config_path)
|
|
257
|
+
return _apply_env_overrides(RuntimeConfig())
|
|
258
|
+
|
|
259
|
+
return _apply_env_overrides(_dict_to_runtime_config(data, config_path))
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _dict_to_runtime_config(data: dict[str, Any], source: Path) -> RuntimeConfig:
|
|
263
|
+
"""Convert a dict to RuntimeConfig, warning on unknown keys.
|
|
264
|
+
|
|
265
|
+
System boundary: user-edited config. Strict on value validation, best-effort
|
|
266
|
+
on unknown keys for forward compat (coding-standards.md §5, system boundaries).
|
|
267
|
+
"""
|
|
268
|
+
known_fields = {f.name for f in fields(RuntimeConfig)}
|
|
269
|
+
unknown = set(data.keys()) - known_fields
|
|
270
|
+
if unknown:
|
|
271
|
+
logger.warning(
|
|
272
|
+
"Unknown keys in %s (ignored): %s",
|
|
273
|
+
source,
|
|
274
|
+
", ".join(sorted(unknown)),
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
kwargs: dict[str, Any] = {}
|
|
278
|
+
for f in fields(RuntimeConfig):
|
|
279
|
+
if f.name in data:
|
|
280
|
+
val = data[f.name]
|
|
281
|
+
# YAML parses "off"/"on"/"yes"/"no" as booleans — coerce back
|
|
282
|
+
# for string fields (e.g., log_level: off → False → "off")
|
|
283
|
+
if isinstance(val, bool) and f.type in ("str", str):
|
|
284
|
+
val = "on" if val else "off"
|
|
285
|
+
# Coerce quoted strings to bool for bool fields
|
|
286
|
+
# (auth_ignore_env: "false" should be False, not truthy string)
|
|
287
|
+
elif isinstance(val, str) and f.type in (bool, "bool"):
|
|
288
|
+
low = val.strip().lower()
|
|
289
|
+
if low in {"1", "true", "yes", "on"}:
|
|
290
|
+
val = True
|
|
291
|
+
elif low in {"0", "false", "no", "off", ""}:
|
|
292
|
+
val = False
|
|
293
|
+
else:
|
|
294
|
+
logger.warning("Invalid boolean for %s: %r — using default", f.name, val)
|
|
295
|
+
continue
|
|
296
|
+
kwargs[f.name] = val
|
|
297
|
+
|
|
298
|
+
try:
|
|
299
|
+
return RuntimeConfig(**kwargs)
|
|
300
|
+
except (ValueError, TypeError) as e:
|
|
301
|
+
logger.warning("Invalid config in %s: %s — using defaults", source, e)
|
|
302
|
+
return RuntimeConfig()
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def get_runtime_config() -> RuntimeConfig:
|
|
306
|
+
"""Get cached runtime config singleton (lazy-loaded on first access)."""
|
|
307
|
+
global _config
|
|
308
|
+
if _config is None:
|
|
309
|
+
_config = load_runtime_config()
|
|
310
|
+
return _config
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def get_default_direct_model() -> str | None:
|
|
314
|
+
"""Get the configured direct-session model override, or None if unset."""
|
|
315
|
+
return get_runtime_config().default_direct_model.strip() or None
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def reset_runtime_config() -> None:
|
|
319
|
+
"""Reset the cached singleton (for testing)."""
|
|
320
|
+
global _config
|
|
321
|
+
_config = None
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def write_runtime_config(config_data: dict[str, Any], path: Path | None = None) -> Path:
|
|
325
|
+
"""Write runtime config to YAML file atomically.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
config_data: Dict of config values to write.
|
|
329
|
+
path: Override path (for testing).
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
Path to the written file.
|
|
333
|
+
"""
|
|
334
|
+
config_path = path or get_config_path()
|
|
335
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
336
|
+
|
|
337
|
+
from ruamel.yaml import YAML
|
|
338
|
+
|
|
339
|
+
ruamel = YAML()
|
|
340
|
+
ruamel.preserve_quotes = True
|
|
341
|
+
ruamel.default_flow_style = False
|
|
342
|
+
|
|
343
|
+
# Atomic write: unique temp file + os.replace (matches proxy config pattern)
|
|
344
|
+
fd, tmp_path = tempfile.mkstemp(
|
|
345
|
+
dir=str(config_path.parent),
|
|
346
|
+
prefix=f".{config_path.stem}.",
|
|
347
|
+
suffix=".tmp",
|
|
348
|
+
)
|
|
349
|
+
try:
|
|
350
|
+
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
|
351
|
+
ruamel.dump(config_data, f)
|
|
352
|
+
os.chmod(tmp_path, 0o600)
|
|
353
|
+
os.replace(tmp_path, str(config_path))
|
|
354
|
+
except Exception:
|
|
355
|
+
try:
|
|
356
|
+
os.unlink(tmp_path)
|
|
357
|
+
except OSError:
|
|
358
|
+
pass
|
|
359
|
+
raise
|
|
360
|
+
|
|
361
|
+
reset_runtime_config()
|
|
362
|
+
|
|
363
|
+
return config_path
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def get_default_config_content() -> str:
|
|
367
|
+
"""Generate default config.yaml content with comments."""
|
|
368
|
+
return """\
|
|
369
|
+
# Forge Runtime Configuration
|
|
370
|
+
# This file is optional — Forge works with built-in defaults.
|
|
371
|
+
# Edit with: forge config edit
|
|
372
|
+
# Set values: forge config set <key>=<value>
|
|
373
|
+
|
|
374
|
+
# Proxy execution mode:
|
|
375
|
+
# host — proxy runs on host (default, no Docker required)
|
|
376
|
+
# sidecar — proxy bundled with Claude in Docker container
|
|
377
|
+
proxy_mode: host
|
|
378
|
+
|
|
379
|
+
# Docker image for sidecar mode
|
|
380
|
+
# sidecar_image: forge-sidecar:latest
|
|
381
|
+
|
|
382
|
+
# Version string for User-Agent header to upstream LLM providers
|
|
383
|
+
# user_agent_claude_code_version: "2.1.76"
|
|
384
|
+
|
|
385
|
+
# Optional model override for direct (non-proxy) sessions.
|
|
386
|
+
# Forge pins this through Claude Code's ANTHROPIC_DEFAULT_*_MODEL env vars.
|
|
387
|
+
# Set to "" to let Claude Code pick. Aliases like "opus" or "sonnet" also work.
|
|
388
|
+
# default_direct_model: claude-opus-4-6
|
|
389
|
+
|
|
390
|
+
# Fallback auto-compact window for proxy mode when model lookup fails.
|
|
391
|
+
# Passed as CLAUDE_CODE_AUTO_COMPACT_WINDOW to Claude Code.
|
|
392
|
+
# Direct sessions don't use this — Claude Code handles its own context.
|
|
393
|
+
# context_limit: 200000
|
|
394
|
+
|
|
395
|
+
# Status line timeout for proxy/git calls (seconds)
|
|
396
|
+
# status_timeout: 2.0
|
|
397
|
+
|
|
398
|
+
# Handoff agent timeout (seconds)
|
|
399
|
+
# handoff_timeout: 300
|
|
400
|
+
|
|
401
|
+
# File logging level: off (no file logging), debug, info, warning
|
|
402
|
+
# Logs written to $FORGE_HOME/logs/
|
|
403
|
+
# Override: FORGE_DEBUG env var (1/true/yes for debug, 0/false/no/off to disable)
|
|
404
|
+
# log_level: "off"
|
|
405
|
+
|
|
406
|
+
# Show Claude.ai rate limit usage in status line (direct sessions only).
|
|
407
|
+
# Not relevant for enterprise plans. Enable with: forge config set show_rate_limits=true
|
|
408
|
+
# show_rate_limits: false
|
|
409
|
+
|
|
410
|
+
# Auto-delete log files older than N days on CLI startup.
|
|
411
|
+
# 0 = disabled (no auto-cleanup). Example: 30 = keep last 30 days.
|
|
412
|
+
# Manual cleanup: forge logs --clean [--older-than DAYS]
|
|
413
|
+
# log_retention_days: 0
|
|
414
|
+
|
|
415
|
+
# Auto-delete sessions older than N days on CLI startup.
|
|
416
|
+
# 0 = disabled (no auto-cleanup). Example: 90 = keep last 90 days.
|
|
417
|
+
# Keeps worktrees and branches; removes manifests, index entries, and
|
|
418
|
+
# Claude transcripts (*.jsonl in ~/.claude/projects/).
|
|
419
|
+
# Forge artifact snapshots (.forge/artifacts/) are NOT removed.
|
|
420
|
+
# Manual cleanup: forge session clean --older-than DAYS
|
|
421
|
+
# session_retention_days: 0
|
|
422
|
+
|
|
423
|
+
# Policy summary feedback: show post-evaluation summary lines and additionalContext.
|
|
424
|
+
# "on" (default) prints what was checked and the verdict after each policy evaluation.
|
|
425
|
+
# "off" silences summary lines. Deny messages and substantive warnings stay visible always.
|
|
426
|
+
# policy_summary_feedback: "on"
|
|
427
|
+
|
|
428
|
+
# Tool failure telemetry for proxied sessions.
|
|
429
|
+
# Records failed tool call inputs and errors to help refine model-family prompt addendums.
|
|
430
|
+
# Off by default because payloads may include file paths, command text, or content snippets.
|
|
431
|
+
# log_tool_failures: false
|
|
432
|
+
|
|
433
|
+
# Ignore environment variables for credential resolution.
|
|
434
|
+
# When true, Forge reads credentials only from ~/.forge/credentials.yaml.
|
|
435
|
+
# Useful when your shell ANTHROPIC_API_KEY is for Claude Code (OAuth/Max),
|
|
436
|
+
# but you want Forge subprocesses to use a separate key from the credential file.
|
|
437
|
+
# auth_ignore_env: false
|
|
438
|
+
"""
|
forge/search/__init__.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Forge search infrastructure for transcript indexing and search.
|
|
2
|
+
|
|
3
|
+
Provides:
|
|
4
|
+
- search_from_index(): BM25 search using persistent precomputed index
|
|
5
|
+
- search(): Legacy BM25 search (builds index at query time)
|
|
6
|
+
- extract_document() / decompose_document(): Content extraction and decomposition
|
|
7
|
+
- BM25IndexStore / ContentStore / SearchDocumentStore: Per-project persistence
|
|
8
|
+
- tokenize(): Shared tokenizer for BM25 indexing and querying
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .bm25_store import BM25IndexData, BM25IndexStore
|
|
12
|
+
from .content_store import ContentStore
|
|
13
|
+
from .engine import SearchResult, search, search_from_index
|
|
14
|
+
from .exceptions import (
|
|
15
|
+
BM25IndexCorruptedError,
|
|
16
|
+
ContentStoreCorruptedError,
|
|
17
|
+
IndexStateCorruptedError,
|
|
18
|
+
SearchDocumentStoreCorruptedError,
|
|
19
|
+
SearchError,
|
|
20
|
+
)
|
|
21
|
+
from .extractor import (
|
|
22
|
+
SearchDocument,
|
|
23
|
+
SearchDocumentMeta,
|
|
24
|
+
decompose_document,
|
|
25
|
+
extract_document,
|
|
26
|
+
)
|
|
27
|
+
from .index_state import IndexState, IndexStateStore
|
|
28
|
+
from .store import SearchDocumentStore
|
|
29
|
+
from .tokenizer import tokenize
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
# Core API
|
|
33
|
+
"search",
|
|
34
|
+
"search_from_index",
|
|
35
|
+
"extract_document",
|
|
36
|
+
"decompose_document",
|
|
37
|
+
"tokenize",
|
|
38
|
+
# Types
|
|
39
|
+
"SearchResult",
|
|
40
|
+
"SearchDocument",
|
|
41
|
+
"SearchDocumentMeta",
|
|
42
|
+
"BM25IndexData",
|
|
43
|
+
# Stores
|
|
44
|
+
"SearchDocumentStore",
|
|
45
|
+
"BM25IndexStore",
|
|
46
|
+
"ContentStore",
|
|
47
|
+
"IndexStateStore",
|
|
48
|
+
"IndexState",
|
|
49
|
+
# Exceptions
|
|
50
|
+
"SearchError",
|
|
51
|
+
"IndexStateCorruptedError",
|
|
52
|
+
"SearchDocumentStoreCorruptedError",
|
|
53
|
+
"BM25IndexCorruptedError",
|
|
54
|
+
"ContentStoreCorruptedError",
|
|
55
|
+
]
|