specsmith 0.3.0.dev128__tar.gz → 0.3.0.dev129__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {specsmith-0.3.0.dev128/src/specsmith.egg-info → specsmith-0.3.0.dev129}/PKG-INFO +5 -3
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/pyproject.toml +3 -2
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/agent/providers/__init__.py +12 -2
- specsmith-0.3.0.dev129/src/specsmith/agent/providers/mistral.py +175 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/agent/runner.py +11 -14
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/agent/tools.py +300 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129/src/specsmith.egg-info}/PKG-INFO +5 -3
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith.egg-info/SOURCES.txt +1 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith.egg-info/requires.txt +4 -1
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/LICENSE +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/README.md +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/setup.cfg +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/epistemic/__init__.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/epistemic/belief.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/epistemic/certainty.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/epistemic/failure_graph.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/epistemic/py.typed +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/epistemic/recovery.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/epistemic/session.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/epistemic/stress_tester.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/epistemic/trace.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/__init__.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/__main__.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/agent/__init__.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/agent/core.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/agent/hooks.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/agent/profiles/epistemic-auditor.md +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/agent/profiles/planner.md +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/agent/profiles/verifier.md +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/agent/providers/anthropic.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/agent/providers/gemini.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/agent/providers/ollama.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/agent/providers/openai.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/agent/skills.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/architect.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/auditor.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/auth.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/cli.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/commands/__init__.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/compressor.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/config.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/credit_analyzer.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/credits.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/differ.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/doctor.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/epistemic/__init__.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/epistemic/belief.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/epistemic/certainty.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/epistemic/failure_graph.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/epistemic/recovery.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/epistemic/stress_tester.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/executor.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/exporter.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/importer.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/integrations/__init__.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/integrations/aider.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/integrations/base.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/integrations/claude_code.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/integrations/copilot.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/integrations/cursor.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/integrations/gemini.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/integrations/warp.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/integrations/windsurf.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/ledger.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/patent.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/plugins.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/rate_limits.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/releaser.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/requirements.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/scaffolder.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/session.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/agents.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/community/bug_report.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/community/code_of_conduct.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/community/contributing.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/community/feature_request.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/community/license-Apache-2.0.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/community/license-MIT.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/community/pull_request_template.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/community/security.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/docs/architecture.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/docs/mkdocs.yml.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/docs/readthedocs.yaml.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/docs/requirements.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/docs/test-spec.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/docs/workflow.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/editorconfig.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/gitattributes.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/gitignore.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/go/go.mod.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/go/main.go.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/governance/belief-registry.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/governance/context-budget.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/governance/drift-metrics.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/governance/epistemic-axioms.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/governance/failure-modes.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/governance/roles.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/governance/rules.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/governance/uncertainty-map.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/governance/verification.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/governance/workflow.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/js/package.json.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/ledger.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/python/cli.py.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/python/init.py.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/python/pyproject.toml.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/readme.md.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/rust/Cargo.toml.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/rust/main.rs.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/scripts/exec.cmd.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/scripts/exec.sh.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/scripts/run.cmd.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/scripts/run.sh.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/scripts/setup.cmd.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/scripts/setup.sh.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/workflows/release.yml.j2 +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/tools.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/trace.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/updater.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/upgrader.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/validator.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/vcs/__init__.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/vcs/base.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/vcs/bitbucket.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/vcs/github.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/vcs/gitlab.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/vcs_commands.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/workspace.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith.egg-info/dependency_links.txt +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith.egg-info/entry_points.txt +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith.egg-info/top_level.txt +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/tests/test_auditor.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/tests/test_cli.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/tests/test_compressor.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/tests/test_epistemic.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/tests/test_importer.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/tests/test_integrations.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/tests/test_rate_limits.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/tests/test_scaffolder.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/tests/test_smoke.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/tests/test_tools.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/tests/test_validator.py +0 -0
- {specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/tests/test_vcs.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: specsmith
|
|
3
|
-
Version: 0.3.0.
|
|
3
|
+
Version: 0.3.0.dev129
|
|
4
4
|
Summary: Applied Epistemic Engineering toolkit — forge epistemically-governed scaffolds, stress-test belief systems, and run AEE pipelines.
|
|
5
5
|
Author: BitConcepts
|
|
6
6
|
License-Expression: MIT
|
|
@@ -41,12 +41,14 @@ Requires-Dist: types-pyyaml>=6.0; extra == "dev"
|
|
|
41
41
|
Provides-Extra: docs
|
|
42
42
|
Requires-Dist: mkdocs>=1.6; extra == "docs"
|
|
43
43
|
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
|
|
44
|
-
Provides-Extra:
|
|
45
|
-
Requires-Dist: anthropic>=0.56; extra == "
|
|
44
|
+
Provides-Extra: anthropic
|
|
45
|
+
Requires-Dist: anthropic>=0.56; extra == "anthropic"
|
|
46
46
|
Provides-Extra: openai
|
|
47
47
|
Requires-Dist: openai>=1.0; extra == "openai"
|
|
48
48
|
Provides-Extra: gemini
|
|
49
49
|
Requires-Dist: google-generativeai>=0.8; extra == "gemini"
|
|
50
|
+
Provides-Extra: mistral
|
|
51
|
+
Requires-Dist: openai>=1.0; extra == "mistral"
|
|
50
52
|
Provides-Extra: agent
|
|
51
53
|
Requires-Dist: anthropic>=0.56; extra == "agent"
|
|
52
54
|
Requires-Dist: openai>=1.0; extra == "agent"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specsmith"
|
|
7
|
-
version = "0.3.0.
|
|
7
|
+
version = "0.3.0.dev129"
|
|
8
8
|
description = "Applied Epistemic Engineering toolkit — forge epistemically-governed scaffolds, stress-test belief systems, and run AEE pipelines."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -55,9 +55,10 @@ docs = [
|
|
|
55
55
|
"mkdocs-material>=9.5",
|
|
56
56
|
]
|
|
57
57
|
# LLM provider extras for specsmith run (agentic client)
|
|
58
|
-
|
|
58
|
+
anthropic = ["anthropic>=0.56"]
|
|
59
59
|
openai = ["openai>=1.0"]
|
|
60
60
|
gemini = ["google-generativeai>=0.8"]
|
|
61
|
+
mistral = ["openai>=1.0"] # Mistral uses the openai SDK pointed at api.mistral.ai
|
|
61
62
|
# Install all optional LLM providers
|
|
62
63
|
agent = ["anthropic>=0.56", "openai>=1.0"]
|
|
63
64
|
# Convenience bundle: everything
|
|
@@ -37,7 +37,7 @@ def get_provider(
|
|
|
37
37
|
"""Get a configured LLM provider.
|
|
38
38
|
|
|
39
39
|
Args:
|
|
40
|
-
provider_name: "anthropic", "openai", "gemini", "ollama", or None (auto-detect)
|
|
40
|
+
provider_name: "anthropic", "openai", "gemini", "mistral", "ollama", or None (auto-detect)
|
|
41
41
|
model: specific model name, or None (use tier default)
|
|
42
42
|
tier: ModelTier.FAST / BALANCED / POWERFUL
|
|
43
43
|
base_url: override API base URL (for OpenAI-compatible proxies)
|
|
@@ -76,6 +76,13 @@ def get_provider(
|
|
|
76
76
|
model=resolved_model or "gemini-2.5-pro",
|
|
77
77
|
api_key=api_key or os.environ.get("GOOGLE_API_KEY", ""),
|
|
78
78
|
)
|
|
79
|
+
elif provider_name == "mistral":
|
|
80
|
+
from specsmith.agent.providers.mistral import MistralProvider
|
|
81
|
+
|
|
82
|
+
return MistralProvider(
|
|
83
|
+
model=resolved_model or "mistral-large-latest",
|
|
84
|
+
api_key=api_key or os.environ.get("MISTRAL_API_KEY", ""),
|
|
85
|
+
)
|
|
79
86
|
elif provider_name == "ollama":
|
|
80
87
|
from specsmith.agent.providers.ollama import OllamaProvider
|
|
81
88
|
|
|
@@ -85,7 +92,7 @@ def get_provider(
|
|
|
85
92
|
)
|
|
86
93
|
else:
|
|
87
94
|
raise ValueError(
|
|
88
|
-
f"Unknown provider '{provider_name}'. Valid: anthropic, openai, gemini, ollama"
|
|
95
|
+
f"Unknown provider '{provider_name}'. Valid: anthropic, openai, gemini, mistral, ollama"
|
|
89
96
|
)
|
|
90
97
|
|
|
91
98
|
|
|
@@ -101,6 +108,8 @@ def _auto_detect_provider() -> str:
|
|
|
101
108
|
return "openai"
|
|
102
109
|
if os.environ.get("GOOGLE_API_KEY"):
|
|
103
110
|
return "gemini"
|
|
111
|
+
if os.environ.get("MISTRAL_API_KEY"):
|
|
112
|
+
return "mistral"
|
|
104
113
|
|
|
105
114
|
# Try Ollama (no API key needed)
|
|
106
115
|
import urllib.request
|
|
@@ -126,6 +135,7 @@ def list_providers() -> list[dict[str, str]]:
|
|
|
126
135
|
("anthropic", "ANTHROPIC_API_KEY"),
|
|
127
136
|
("openai", "OPENAI_API_KEY"),
|
|
128
137
|
("gemini", "GOOGLE_API_KEY"),
|
|
138
|
+
("mistral", "MISTRAL_API_KEY"),
|
|
129
139
|
("ollama", ""),
|
|
130
140
|
]:
|
|
131
141
|
if name == "ollama":
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 BitConcepts, LLC. All rights reserved.
|
|
3
|
+
"""Mistral AI provider for the specsmith agentic client.
|
|
4
|
+
|
|
5
|
+
Mistral's API is OpenAI-compatible, so we use the openai SDK pointed at
|
|
6
|
+
Mistral's endpoint. Pixtral models support vision/OCR.
|
|
7
|
+
|
|
8
|
+
Requires: pip install specsmith[mistral]
|
|
9
|
+
|
|
10
|
+
Environment:
|
|
11
|
+
MISTRAL_API_KEY — your Mistral API key (https://console.mistral.ai/)
|
|
12
|
+
|
|
13
|
+
Models:
|
|
14
|
+
mistral-large-latest — most capable text model
|
|
15
|
+
mistral-small-latest — fast, cheap
|
|
16
|
+
codestral-latest — code-optimised (FIM support)
|
|
17
|
+
pixtral-large-latest — vision + OCR (multimodal)
|
|
18
|
+
pixtral-12b-2409 — smaller vision model
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from collections.abc import Iterator
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
from specsmith.agent.core import (
|
|
27
|
+
CompletionResponse,
|
|
28
|
+
Message,
|
|
29
|
+
StreamToken,
|
|
30
|
+
Tool,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
_MISTRAL_BASE_URL = "https://api.mistral.ai/v1"
|
|
34
|
+
|
|
35
|
+
# Pixtral models that support image/document input (OCR use-cases)
|
|
36
|
+
_VISION_MODELS = {"pixtral-large-latest", "pixtral-12b-2409", "pixtral-large-2411"}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class MistralProvider:
|
|
40
|
+
"""Mistral AI provider. Uses the OpenAI-compatible chat completions API."""
|
|
41
|
+
|
|
42
|
+
provider_name = "mistral"
|
|
43
|
+
|
|
44
|
+
def __init__(self, model: str = "mistral-large-latest", api_key: str = "") -> None:
|
|
45
|
+
self.model = model
|
|
46
|
+
self._api_key = api_key
|
|
47
|
+
self._client: Any = None
|
|
48
|
+
self._ensure_client()
|
|
49
|
+
|
|
50
|
+
def _ensure_client(self) -> None:
|
|
51
|
+
try:
|
|
52
|
+
import openai
|
|
53
|
+
|
|
54
|
+
self._client = openai.OpenAI(
|
|
55
|
+
api_key=self._api_key or "placeholder",
|
|
56
|
+
base_url=_MISTRAL_BASE_URL,
|
|
57
|
+
)
|
|
58
|
+
except ImportError as e:
|
|
59
|
+
from specsmith.agent.core import ProviderNotAvailable
|
|
60
|
+
|
|
61
|
+
raise ProviderNotAvailable("mistral", "openai") from e
|
|
62
|
+
|
|
63
|
+
def is_available(self) -> bool:
|
|
64
|
+
try:
|
|
65
|
+
import openai # noqa: F401
|
|
66
|
+
|
|
67
|
+
return bool(self._api_key)
|
|
68
|
+
except ImportError:
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def supports_vision(self) -> bool:
|
|
73
|
+
return self.model in _VISION_MODELS
|
|
74
|
+
|
|
75
|
+
def complete(
|
|
76
|
+
self,
|
|
77
|
+
messages: list[Message],
|
|
78
|
+
tools: list[Tool] | None = None,
|
|
79
|
+
max_tokens: int = 4096,
|
|
80
|
+
) -> CompletionResponse:
|
|
81
|
+
msgs = [m.to_dict() for m in messages]
|
|
82
|
+
kwargs: dict[str, Any] = {
|
|
83
|
+
"model": self.model,
|
|
84
|
+
"messages": msgs,
|
|
85
|
+
"max_tokens": max_tokens,
|
|
86
|
+
}
|
|
87
|
+
if tools:
|
|
88
|
+
kwargs["tools"] = [t.to_openai_schema() for t in tools]
|
|
89
|
+
kwargs["tool_choice"] = "auto"
|
|
90
|
+
|
|
91
|
+
response = self._client.chat.completions.create(**kwargs)
|
|
92
|
+
choice = response.choices[0]
|
|
93
|
+
content = choice.message.content or ""
|
|
94
|
+
|
|
95
|
+
tool_calls: list[dict[str, Any]] = []
|
|
96
|
+
if choice.message.tool_calls:
|
|
97
|
+
for tc in choice.message.tool_calls:
|
|
98
|
+
import json
|
|
99
|
+
|
|
100
|
+
tool_calls.append(
|
|
101
|
+
{
|
|
102
|
+
"id": tc.id,
|
|
103
|
+
"name": tc.function.name,
|
|
104
|
+
"input": json.loads(tc.function.arguments or "{}"),
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
usage = response.usage
|
|
109
|
+
return CompletionResponse(
|
|
110
|
+
content=content,
|
|
111
|
+
model=response.model,
|
|
112
|
+
input_tokens=usage.prompt_tokens if usage else 0,
|
|
113
|
+
output_tokens=usage.completion_tokens if usage else 0,
|
|
114
|
+
tool_calls=tool_calls,
|
|
115
|
+
stop_reason=choice.finish_reason or "stop",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def stream(
|
|
119
|
+
self,
|
|
120
|
+
messages: list[Message],
|
|
121
|
+
tools: list[Tool] | None = None,
|
|
122
|
+
max_tokens: int = 4096,
|
|
123
|
+
) -> Iterator[StreamToken]:
|
|
124
|
+
msgs = [m.to_dict() for m in messages]
|
|
125
|
+
kwargs: dict[str, Any] = {
|
|
126
|
+
"model": self.model,
|
|
127
|
+
"messages": msgs,
|
|
128
|
+
"max_tokens": max_tokens,
|
|
129
|
+
"stream": True,
|
|
130
|
+
}
|
|
131
|
+
stream = self._client.chat.completions.create(**kwargs)
|
|
132
|
+
for chunk in stream:
|
|
133
|
+
delta = chunk.choices[0].delta if chunk.choices else None
|
|
134
|
+
if delta and delta.content:
|
|
135
|
+
yield StreamToken(text=delta.content)
|
|
136
|
+
yield StreamToken(text="", is_final=True)
|
|
137
|
+
|
|
138
|
+
def ocr_image(self, image_path: str, prompt: str = "Extract all text from this image.") -> str:
|
|
139
|
+
"""Extract text from an image using a Pixtral vision model.
|
|
140
|
+
|
|
141
|
+
Automatically switches to pixtral-large-latest if current model is text-only.
|
|
142
|
+
"""
|
|
143
|
+
import base64
|
|
144
|
+
from pathlib import Path
|
|
145
|
+
|
|
146
|
+
model = self.model if self.supports_vision else "pixtral-large-latest"
|
|
147
|
+
img_bytes = Path(image_path).read_bytes()
|
|
148
|
+
b64 = base64.b64encode(img_bytes).decode()
|
|
149
|
+
|
|
150
|
+
# Detect MIME type from extension
|
|
151
|
+
ext = Path(image_path).suffix.lower()
|
|
152
|
+
mime_map = {
|
|
153
|
+
".jpg": "image/jpeg",
|
|
154
|
+
".jpeg": "image/jpeg",
|
|
155
|
+
".png": "image/png",
|
|
156
|
+
".gif": "image/gif",
|
|
157
|
+
".webp": "image/webp",
|
|
158
|
+
".pdf": "application/pdf",
|
|
159
|
+
}
|
|
160
|
+
mime = mime_map.get(ext, "image/png")
|
|
161
|
+
|
|
162
|
+
response = self._client.chat.completions.create(
|
|
163
|
+
model=model,
|
|
164
|
+
messages=[
|
|
165
|
+
{
|
|
166
|
+
"role": "user",
|
|
167
|
+
"content": [
|
|
168
|
+
{"type": "image_url", "image_url": {"url": f"data:{mime};base64,{b64}"}},
|
|
169
|
+
{"type": "text", "text": prompt},
|
|
170
|
+
],
|
|
171
|
+
}
|
|
172
|
+
],
|
|
173
|
+
max_tokens=4096,
|
|
174
|
+
)
|
|
175
|
+
return response.choices[0].message.content or ""
|
|
@@ -320,26 +320,23 @@ class AgentRunner:
|
|
|
320
320
|
return final_response
|
|
321
321
|
|
|
322
322
|
def _call_provider(self, messages: list[Message], silent: bool = False) -> CompletionResponse:
|
|
323
|
-
"""Call the LLM provider, streaming if enabled.
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
323
|
+
"""Call the LLM provider, streaming if enabled.
|
|
324
|
+
|
|
325
|
+
Streaming is disabled when tools are registered because the streaming
|
|
326
|
+
path cannot reliably capture tool_call blocks from the response.
|
|
327
|
+
Non-streaming is always used for tool-bearing turns.
|
|
328
|
+
"""
|
|
329
|
+
provider: Any = self._provider
|
|
330
|
+
use_stream = self._stream and not silent and not self._tools
|
|
331
|
+
if use_stream:
|
|
327
332
|
accumulated = ""
|
|
328
|
-
for token in provider.stream(messages, tools=
|
|
333
|
+
for token in provider.stream(messages, tools=None):
|
|
329
334
|
if token.text:
|
|
330
335
|
self._print(token.text, end="", flush=True)
|
|
331
336
|
accumulated += token.text
|
|
332
337
|
if token.is_final:
|
|
333
338
|
self._print()
|
|
334
|
-
|
|
335
|
-
# For now, do a second call if we didn't get tool calls
|
|
336
|
-
if not accumulated.strip():
|
|
337
|
-
return cast(CompletionResponse, provider.complete(messages, tools=self._tools))
|
|
338
|
-
# Return a synthetic response with the streamed content
|
|
339
|
-
return CompletionResponse(
|
|
340
|
-
content=accumulated,
|
|
341
|
-
model=str(provider.model),
|
|
342
|
-
)
|
|
339
|
+
return CompletionResponse(content=accumulated, model=str(provider.model))
|
|
343
340
|
else:
|
|
344
341
|
response = cast(CompletionResponse, provider.complete(messages, tools=self._tools))
|
|
345
342
|
if not silent and response.content:
|
|
@@ -16,6 +16,10 @@ Tool categories:
|
|
|
16
16
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
|
+
import fnmatch
|
|
20
|
+
import os
|
|
21
|
+
import platform
|
|
22
|
+
import re
|
|
19
23
|
import subprocess
|
|
20
24
|
import sys
|
|
21
25
|
from pathlib import Path
|
|
@@ -316,6 +320,100 @@ def build_tool_registry(project_dir: str = ".") -> list[Tool]:
|
|
|
316
320
|
],
|
|
317
321
|
handler=lambda path, lines="": _read_file_handler(pd, path, lines),
|
|
318
322
|
),
|
|
323
|
+
# ----------------------------------------------------------------
|
|
324
|
+
# File system write tools
|
|
325
|
+
# ----------------------------------------------------------------
|
|
326
|
+
Tool(
|
|
327
|
+
name="write_file",
|
|
328
|
+
description=(
|
|
329
|
+
"Write content to a file (creates or overwrites). "
|
|
330
|
+
"Use for editing source code, docs, config files, etc. "
|
|
331
|
+
"Path is relative to project root."
|
|
332
|
+
),
|
|
333
|
+
params=[
|
|
334
|
+
ToolParam("path", "File path relative to project root"),
|
|
335
|
+
ToolParam("content", "Full content to write to the file"),
|
|
336
|
+
],
|
|
337
|
+
handler=lambda path, content: _write_file_handler(pd, path, content),
|
|
338
|
+
),
|
|
339
|
+
Tool(
|
|
340
|
+
name="list_dir",
|
|
341
|
+
description=(
|
|
342
|
+
"List files and directories. Shows names, sizes, and types. "
|
|
343
|
+
"Use to explore project structure before reading files."
|
|
344
|
+
),
|
|
345
|
+
params=[
|
|
346
|
+
ToolParam(
|
|
347
|
+
"path",
|
|
348
|
+
"Directory path relative to project root (default: root)",
|
|
349
|
+
required=False,
|
|
350
|
+
),
|
|
351
|
+
ToolParam(
|
|
352
|
+
"pattern",
|
|
353
|
+
"Glob pattern to filter (e.g. '*.py', '*.md')",
|
|
354
|
+
required=False,
|
|
355
|
+
),
|
|
356
|
+
],
|
|
357
|
+
handler=lambda path=".", pattern="": _list_dir_handler(pd, path, pattern),
|
|
358
|
+
),
|
|
359
|
+
Tool(
|
|
360
|
+
name="grep_files",
|
|
361
|
+
description=(
|
|
362
|
+
"Search for a regex pattern across files in the project. "
|
|
363
|
+
"Returns matching lines with file:line references. "
|
|
364
|
+
"Essential for finding where things are defined or used."
|
|
365
|
+
),
|
|
366
|
+
params=[
|
|
367
|
+
ToolParam("pattern", "Regex pattern to search for"),
|
|
368
|
+
ToolParam(
|
|
369
|
+
"path",
|
|
370
|
+
"Directory or file to search (relative to root, default: root)",
|
|
371
|
+
required=False,
|
|
372
|
+
),
|
|
373
|
+
ToolParam(
|
|
374
|
+
"glob",
|
|
375
|
+
"File glob filter e.g. '*.py' (default: all text files)",
|
|
376
|
+
required=False,
|
|
377
|
+
),
|
|
378
|
+
ToolParam(
|
|
379
|
+
"ignore_case",
|
|
380
|
+
"'true' for case-insensitive search",
|
|
381
|
+
required=False,
|
|
382
|
+
),
|
|
383
|
+
],
|
|
384
|
+
handler=lambda pattern, path=".", glob="", ignore_case="false": _grep_handler(
|
|
385
|
+
pd, pattern, path, glob, ignore_case
|
|
386
|
+
),
|
|
387
|
+
),
|
|
388
|
+
# ----------------------------------------------------------------
|
|
389
|
+
# Shell execution — the most powerful tool
|
|
390
|
+
# ----------------------------------------------------------------
|
|
391
|
+
Tool(
|
|
392
|
+
name="run_command",
|
|
393
|
+
description=(
|
|
394
|
+
"Execute a shell command in the project directory and return stdout+stderr. "
|
|
395
|
+
"Use for: running tests (pytest), linting (ruff), building, git operations, "
|
|
396
|
+
"installing packages, checking file contents with CLI tools, anything. "
|
|
397
|
+
"Cross-platform: automatically uses PowerShell on Windows, bash on Linux/macOS. "
|
|
398
|
+
"Commands run with a 120-second timeout."
|
|
399
|
+
),
|
|
400
|
+
params=[
|
|
401
|
+
ToolParam("command", "The shell command to execute"),
|
|
402
|
+
ToolParam(
|
|
403
|
+
"working_dir",
|
|
404
|
+
"Working directory relative to project root (default: root)",
|
|
405
|
+
required=False,
|
|
406
|
+
),
|
|
407
|
+
ToolParam(
|
|
408
|
+
"timeout",
|
|
409
|
+
"Timeout in seconds (default 120, max 300)",
|
|
410
|
+
required=False,
|
|
411
|
+
),
|
|
412
|
+
],
|
|
413
|
+
handler=lambda command, working_dir=".", timeout="120": _run_command_handler(
|
|
414
|
+
pd, command, working_dir, timeout
|
|
415
|
+
),
|
|
416
|
+
),
|
|
319
417
|
]
|
|
320
418
|
|
|
321
419
|
return tools
|
|
@@ -350,5 +448,207 @@ def _read_file_handler(project_dir: str, path: str, lines: str = "") -> str:
|
|
|
350
448
|
return f"[ERROR] {e}"
|
|
351
449
|
|
|
352
450
|
|
|
451
|
+
def _write_file_handler(project_dir: str, path: str, content: str) -> str:
|
|
452
|
+
"""Write content to a file within the project directory."""
|
|
453
|
+
root = Path(project_dir).resolve()
|
|
454
|
+
target = (root / path).resolve()
|
|
455
|
+
try:
|
|
456
|
+
target.relative_to(root)
|
|
457
|
+
except ValueError:
|
|
458
|
+
return f"[ERROR] Path '{path}' is outside the project directory"
|
|
459
|
+
try:
|
|
460
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
461
|
+
target.write_text(content, encoding="utf-8")
|
|
462
|
+
size = len(content.encode("utf-8"))
|
|
463
|
+
lines = content.count("\n") + 1
|
|
464
|
+
return f"Written: {path} ({lines} lines, {size} bytes)"
|
|
465
|
+
except Exception as e: # noqa: BLE001
|
|
466
|
+
return f"[ERROR] {e}"
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _list_dir_handler(project_dir: str, path: str = ".", pattern: str = "") -> str:
|
|
470
|
+
"""List directory contents within the project."""
|
|
471
|
+
root = Path(project_dir).resolve()
|
|
472
|
+
target = (root / path).resolve()
|
|
473
|
+
try:
|
|
474
|
+
target.relative_to(root)
|
|
475
|
+
except ValueError:
|
|
476
|
+
return f"[ERROR] Path '{path}' is outside the project directory"
|
|
477
|
+
if not target.exists():
|
|
478
|
+
return f"[NOT FOUND] {path}"
|
|
479
|
+
if not target.is_dir():
|
|
480
|
+
return f"[NOT A DIR] {path}"
|
|
481
|
+
try:
|
|
482
|
+
entries = sorted(target.iterdir(), key=lambda p: (p.is_file(), p.name.lower()))
|
|
483
|
+
lines = []
|
|
484
|
+
for entry in entries:
|
|
485
|
+
if pattern and not fnmatch.fnmatch(entry.name, pattern):
|
|
486
|
+
continue
|
|
487
|
+
if entry.is_dir():
|
|
488
|
+
lines.append(f" {'DIR':>6} {entry.name}/")
|
|
489
|
+
else:
|
|
490
|
+
size = entry.stat().st_size
|
|
491
|
+
size_str = f"{size:,}" if size < 1_000_000 else f"{size // 1024:,}K"
|
|
492
|
+
lines.append(f" {size_str:>6} {entry.name}")
|
|
493
|
+
header = f"{path}/" if not path.endswith("/") else path
|
|
494
|
+
return f"{header}\n" + "\n".join(lines) if lines else f"{header} (empty)"
|
|
495
|
+
except Exception as e: # noqa: BLE001
|
|
496
|
+
return f"[ERROR] {e}"
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def _grep_handler(
|
|
500
|
+
project_dir: str,
|
|
501
|
+
pattern: str,
|
|
502
|
+
path: str = ".",
|
|
503
|
+
glob: str = "",
|
|
504
|
+
ignore_case: str = "false",
|
|
505
|
+
) -> str:
|
|
506
|
+
"""Search for a regex pattern in files within the project."""
|
|
507
|
+
root = Path(project_dir).resolve()
|
|
508
|
+
target = (root / path).resolve()
|
|
509
|
+
try:
|
|
510
|
+
target.relative_to(root)
|
|
511
|
+
except ValueError:
|
|
512
|
+
return f"[ERROR] Path '{path}' is outside the project directory"
|
|
513
|
+
|
|
514
|
+
flags = re.IGNORECASE if ignore_case.lower() == "true" else 0
|
|
515
|
+
try:
|
|
516
|
+
compiled = re.compile(pattern, flags)
|
|
517
|
+
except re.error as e:
|
|
518
|
+
return f"[ERROR] Invalid regex: {e}"
|
|
519
|
+
|
|
520
|
+
_TEXT_EXTENSIONS = {
|
|
521
|
+
".py",
|
|
522
|
+
".md",
|
|
523
|
+
".txt",
|
|
524
|
+
".yml",
|
|
525
|
+
".yaml",
|
|
526
|
+
".toml",
|
|
527
|
+
".json",
|
|
528
|
+
".js",
|
|
529
|
+
".ts",
|
|
530
|
+
".html",
|
|
531
|
+
".css",
|
|
532
|
+
".sh",
|
|
533
|
+
".ps1",
|
|
534
|
+
".cmd",
|
|
535
|
+
".bat",
|
|
536
|
+
".rs",
|
|
537
|
+
".go",
|
|
538
|
+
".c",
|
|
539
|
+
".cpp",
|
|
540
|
+
".h",
|
|
541
|
+
".java",
|
|
542
|
+
".rb",
|
|
543
|
+
".php",
|
|
544
|
+
".tf",
|
|
545
|
+
".ini",
|
|
546
|
+
".cfg",
|
|
547
|
+
".conf",
|
|
548
|
+
}
|
|
549
|
+
_SKIP_DIRS = {".git", "__pycache__", ".venv", "node_modules", ".mypy_cache", "dist", "build"}
|
|
550
|
+
|
|
551
|
+
results: list[str] = []
|
|
552
|
+
files_searched = 0
|
|
553
|
+
|
|
554
|
+
def search_file(fp: Path) -> None:
|
|
555
|
+
nonlocal files_searched
|
|
556
|
+
if glob and not fnmatch.fnmatch(fp.name, glob):
|
|
557
|
+
return
|
|
558
|
+
if not glob and fp.suffix.lower() not in _TEXT_EXTENSIONS:
|
|
559
|
+
return
|
|
560
|
+
try:
|
|
561
|
+
text = fp.read_text(encoding="utf-8", errors="ignore")
|
|
562
|
+
files_searched += 1
|
|
563
|
+
rel = fp.relative_to(root)
|
|
564
|
+
for i, line in enumerate(text.splitlines(), 1):
|
|
565
|
+
if compiled.search(line):
|
|
566
|
+
results.append(f"{rel}:{i}: {line.rstrip()}")
|
|
567
|
+
if len(results) >= 200:
|
|
568
|
+
return
|
|
569
|
+
except Exception: # noqa: BLE001
|
|
570
|
+
pass
|
|
571
|
+
|
|
572
|
+
if target.is_file():
|
|
573
|
+
search_file(target)
|
|
574
|
+
else:
|
|
575
|
+
for dirpath, dirnames, filenames in os.walk(target):
|
|
576
|
+
dirnames[:] = [d for d in dirnames if d not in _SKIP_DIRS]
|
|
577
|
+
for fname in sorted(filenames):
|
|
578
|
+
search_file(Path(dirpath) / fname)
|
|
579
|
+
if len(results) >= 200:
|
|
580
|
+
break
|
|
581
|
+
if len(results) >= 200:
|
|
582
|
+
break
|
|
583
|
+
|
|
584
|
+
if not results:
|
|
585
|
+
return f"No matches for '{pattern}' in {files_searched} file(s) searched."
|
|
586
|
+
summary = f"{len(results)} match(es) across {files_searched} file(s):"
|
|
587
|
+
if len(results) >= 200:
|
|
588
|
+
summary += " (truncated at 200)"
|
|
589
|
+
return summary + "\n" + "\n".join(results)
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def _run_command_handler(
|
|
593
|
+
project_dir: str,
|
|
594
|
+
command: str,
|
|
595
|
+
working_dir: str = ".",
|
|
596
|
+
timeout: str = "120",
|
|
597
|
+
) -> str:
|
|
598
|
+
"""Execute a shell command and return combined stdout+stderr."""
|
|
599
|
+
root = Path(project_dir).resolve()
|
|
600
|
+
cwd = (root / working_dir).resolve()
|
|
601
|
+
try:
|
|
602
|
+
cwd.relative_to(root)
|
|
603
|
+
except ValueError:
|
|
604
|
+
return f"[ERROR] working_dir '{working_dir}' is outside the project directory"
|
|
605
|
+
|
|
606
|
+
try:
|
|
607
|
+
timeout_secs = min(int(timeout), 300)
|
|
608
|
+
except (ValueError, TypeError):
|
|
609
|
+
timeout_secs = 120
|
|
610
|
+
|
|
611
|
+
# Choose shell based on platform
|
|
612
|
+
is_windows = platform.system() == "Windows"
|
|
613
|
+
if is_windows:
|
|
614
|
+
shell_cmd = ["powershell", "-NoProfile", "-NonInteractive", "-Command", command]
|
|
615
|
+
else:
|
|
616
|
+
shell_cmd = ["bash", "-c", command]
|
|
617
|
+
|
|
618
|
+
try:
|
|
619
|
+
result = subprocess.run(
|
|
620
|
+
shell_cmd,
|
|
621
|
+
capture_output=True,
|
|
622
|
+
text=True,
|
|
623
|
+
cwd=str(cwd),
|
|
624
|
+
timeout=timeout_secs,
|
|
625
|
+
)
|
|
626
|
+
output = (result.stdout + result.stderr).strip()
|
|
627
|
+
exit_info = f"[exit {result.returncode}]" if result.returncode != 0 else "[exit 0]"
|
|
628
|
+
if len(output) > 12000:
|
|
629
|
+
output = output[:12000] + f"\n...(truncated, {len(output)} total chars)"
|
|
630
|
+
return f"{exit_info}\n{output}" if output else exit_info
|
|
631
|
+
except subprocess.TimeoutExpired:
|
|
632
|
+
return f"[TIMEOUT] Command exceeded {timeout_secs}s"
|
|
633
|
+
except FileNotFoundError:
|
|
634
|
+
# Shell not found (e.g. bash on Windows) — fall back
|
|
635
|
+
try:
|
|
636
|
+
result = subprocess.run(
|
|
637
|
+
command,
|
|
638
|
+
capture_output=True,
|
|
639
|
+
text=True,
|
|
640
|
+
cwd=str(cwd),
|
|
641
|
+
timeout=timeout_secs,
|
|
642
|
+
shell=True, # noqa: S602
|
|
643
|
+
)
|
|
644
|
+
output = (result.stdout + result.stderr).strip()
|
|
645
|
+
rc = result.returncode
|
|
646
|
+
return f"[exit {rc}]\n{output}" if output else f"[exit {rc}]"
|
|
647
|
+
except Exception as e2: # noqa: BLE001
|
|
648
|
+
return f"[ERROR] {e2}"
|
|
649
|
+
except Exception as e: # noqa: BLE001
|
|
650
|
+
return f"[ERROR] {e}"
|
|
651
|
+
|
|
652
|
+
|
|
353
653
|
def get_tool_by_name(tools: list[Tool], name: str) -> Tool | None:
|
|
354
654
|
return next((t for t in tools if t.name == name), None)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: specsmith
|
|
3
|
-
Version: 0.3.0.
|
|
3
|
+
Version: 0.3.0.dev129
|
|
4
4
|
Summary: Applied Epistemic Engineering toolkit — forge epistemically-governed scaffolds, stress-test belief systems, and run AEE pipelines.
|
|
5
5
|
Author: BitConcepts
|
|
6
6
|
License-Expression: MIT
|
|
@@ -41,12 +41,14 @@ Requires-Dist: types-pyyaml>=6.0; extra == "dev"
|
|
|
41
41
|
Provides-Extra: docs
|
|
42
42
|
Requires-Dist: mkdocs>=1.6; extra == "docs"
|
|
43
43
|
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
|
|
44
|
-
Provides-Extra:
|
|
45
|
-
Requires-Dist: anthropic>=0.56; extra == "
|
|
44
|
+
Provides-Extra: anthropic
|
|
45
|
+
Requires-Dist: anthropic>=0.56; extra == "anthropic"
|
|
46
46
|
Provides-Extra: openai
|
|
47
47
|
Requires-Dist: openai>=1.0; extra == "openai"
|
|
48
48
|
Provides-Extra: gemini
|
|
49
49
|
Requires-Dist: google-generativeai>=0.8; extra == "gemini"
|
|
50
|
+
Provides-Extra: mistral
|
|
51
|
+
Requires-Dist: openai>=1.0; extra == "mistral"
|
|
50
52
|
Provides-Extra: agent
|
|
51
53
|
Requires-Dist: anthropic>=0.56; extra == "agent"
|
|
52
54
|
Requires-Dist: openai>=1.0; extra == "agent"
|
|
@@ -58,6 +58,7 @@ src/specsmith/agent/profiles/verifier.md
|
|
|
58
58
|
src/specsmith/agent/providers/__init__.py
|
|
59
59
|
src/specsmith/agent/providers/anthropic.py
|
|
60
60
|
src/specsmith/agent/providers/gemini.py
|
|
61
|
+
src/specsmith/agent/providers/mistral.py
|
|
61
62
|
src/specsmith/agent/providers/ollama.py
|
|
62
63
|
src/specsmith/agent/providers/openai.py
|
|
63
64
|
src/specsmith/commands/__init__.py
|
|
@@ -16,7 +16,7 @@ pytest-cov>=4.0
|
|
|
16
16
|
ruff>=0.4
|
|
17
17
|
mypy>=1.10
|
|
18
18
|
|
|
19
|
-
[
|
|
19
|
+
[anthropic]
|
|
20
20
|
anthropic>=0.56
|
|
21
21
|
|
|
22
22
|
[dev]
|
|
@@ -34,5 +34,8 @@ mkdocs-material>=9.5
|
|
|
34
34
|
[gemini]
|
|
35
35
|
google-generativeai>=0.8
|
|
36
36
|
|
|
37
|
+
[mistral]
|
|
38
|
+
openai>=1.0
|
|
39
|
+
|
|
37
40
|
[openai]
|
|
38
41
|
openai>=1.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/agent/profiles/epistemic-auditor.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/agent/providers/anthropic.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/community/bug_report.md.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/community/license-MIT.j2
RENAMED
|
File without changes
|
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/community/security.md.j2
RENAMED
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/docs/architecture.md.j2
RENAMED
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/docs/mkdocs.yml.j2
RENAMED
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/docs/readthedocs.yaml.j2
RENAMED
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/docs/requirements.md.j2
RENAMED
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/docs/test-spec.md.j2
RENAMED
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/docs/workflow.md.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/governance/roles.md.j2
RENAMED
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/governance/rules.md.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/governance/workflow.md.j2
RENAMED
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/js/package.json.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/python/pyproject.toml.j2
RENAMED
|
File without changes
|
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/rust/Cargo.toml.j2
RENAMED
|
File without changes
|
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/scripts/exec.cmd.j2
RENAMED
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/scripts/exec.sh.j2
RENAMED
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/scripts/run.cmd.j2
RENAMED
|
File without changes
|
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/scripts/setup.cmd.j2
RENAMED
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/scripts/setup.sh.j2
RENAMED
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith/templates/workflows/release.yml.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specsmith-0.3.0.dev128 → specsmith-0.3.0.dev129}/src/specsmith.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|