specsmith 0.11.5.dev452__tar.gz → 0.11.5.dev453__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.11.5.dev452/src/specsmith.egg-info → specsmith-0.11.5.dev453}/PKG-INFO +1 -1
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/pyproject.toml +1 -1
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/chat_runner.py +54 -9
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/events.py +21 -1
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/runner.py +227 -21
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/cli.py +37 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453/src/specsmith.egg-info}/PKG-INFO +1 -1
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith.egg-info/SOURCES.txt +1 -0
- specsmith-0.11.5.dev453/tests/test_agent_run_feedback.py +355 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/LICENSE +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/README.md +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/setup.cfg +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/epistemic/__init__.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/epistemic/belief.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/epistemic/certainty.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/epistemic/failure_graph.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/epistemic/py.typed +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/epistemic/recovery.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/epistemic/session.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/epistemic/stress_tester.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/epistemic/trace.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/__init__.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/__main__.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/__init__.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/broker.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/cleanup.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/context_seed.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/core.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/dispatch/__init__.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/dispatch/_status.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/dispatch/dag.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/dispatch/dispatcher.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/dispatch/events.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/dispatch/result.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/endpoints.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/execution_profiles.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/fallback.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/hf_leaderboard.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/hf_sync.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/indexer.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/llm_client.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/mcp.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/memory.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/model_intelligence.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/model_profiles.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/orchestrator.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/permissions.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/profiles.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/provider_registry.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/repl.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/router.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/rules.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/safety.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/spawner.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/suggester.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/teams.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/tools.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/verifier.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/agent/voice.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/architect.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/auditor.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/auth.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/block_export.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/channel.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/ci_manager.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/commands/__init__.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/commands/intelligence.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/compliance/__init__.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/compliance/_compat.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/compliance/checker.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/compliance/evidence.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/compliance/regulations.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/compliance/reporter.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/compressor.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/config.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/console_utils.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/context_orchestrator.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/context_window.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/credit_analyzer.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/credits.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/datasources/__init__.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/datasources/base.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/datasources/citations.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/datasources/fpd.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/datasources/odp.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/datasources/patentsview.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/datasources/pfw.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/datasources/ppubs.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/datasources/ptab.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/differ.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/doctor.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/drive.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/editor.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/epistemic/__init__.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/epistemic/belief.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/epistemic/certainty.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/epistemic/failure_graph.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/epistemic/recovery.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/epistemic/stress_tester.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/esdb/__init__.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/esdb/bridge.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/eval/__init__.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/eval/builtins.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/eval/runner.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/executor.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/exporter.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/governance_logic.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/governance_store.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/governance_yaml.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/gui/__init__.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/gui/app.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/gui/main_window.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/gui/session_tab.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/gui/theme.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/gui/widgets/__init__.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/gui/widgets/chat_view.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/gui/widgets/input_bar.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/gui/widgets/provider_bar.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/gui/widgets/token_meter.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/gui/widgets/tool_panel.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/gui/widgets/update_checker.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/gui/worker.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/history_search.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/importer.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/instinct.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/integrations/__init__.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/integrations/agent_skill.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/integrations/aider.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/integrations/base.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/integrations/claude_code.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/integrations/codity.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/integrations/copilot.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/integrations/cursor.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/integrations/gemini.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/integrations/windsurf.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/issue_reporter.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/languages.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/ledger.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/mcp_generator.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/migrations/__init__.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/migrations/m001_governance_yaml.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/migrations/m002_agents_slim.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/migrations/m003_compliance_init.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/migrations/m004_ledger_esdb.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/migrations/m005_agent_run_tool.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/migrations/m006_session_governance.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/migrations/runner.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/ollama_cmds.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/patent.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/paths.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/phase.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/plugins.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/profiles.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/rate_limits.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/releaser.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/requirements.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/requirements_parser.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/retrieval.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/safe_write.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/scaffolder.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/serve.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/session.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/session_init.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/session_store.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/skills/__init__.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/skills/cloud.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/skills/corporate.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/skills/cross_platform.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/skills/devops.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/skills/docs.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/skills/embedded.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/skills/governance.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/skills/hardware.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/skills/mobile.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/skills/productivity.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/skills/ssh.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/skills_builder.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/sync.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/agents.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/community/bug_report.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/community/code_of_conduct.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/community/contributing.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/community/feature_request.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/community/license-Apache-2.0.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/community/license-MIT.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/community/pull_request_template.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/community/security.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/docs/architecture.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/docs/mkdocs.yml.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/docs/readthedocs.yaml.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/docs/requirements.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/docs/test-spec.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/editorconfig.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/gitattributes.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/gitignore.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/go/go.mod.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/go/main.go.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/governance/belief-registry.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/governance/context-budget.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/governance/drift-metrics.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/governance/epistemic-axioms.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/governance/failure-modes.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/governance/lifecycle.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/governance/roles.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/governance/rules.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/governance/session-protocol.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/governance/uncertainty-map.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/governance/verification.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/js/package.json.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/ledger.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/python/cli.py.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/python/init.py.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/python/pyproject.toml.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/readme.md.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/rust/Cargo.toml.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/rust/main.rs.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/scripts/exec.cmd.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/scripts/exec.sh.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/scripts/run.cmd.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/scripts/run.sh.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/scripts/setup.cmd.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/scripts/setup.sh.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/templates/workflows/release.yml.j2 +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/tool_installer.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/toolrules.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/tools.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/trace.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/updater.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/upgrader.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/validator.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/vcs/__init__.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/vcs/base.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/vcs/bitbucket.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/vcs/github.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/vcs/gitlab.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/vcs_commands.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/wireframes.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith/workspace.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith.egg-info/dependency_links.txt +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith.egg-info/entry_points.txt +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith.egg-info/requires.txt +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/src/specsmith.egg-info/top_level.txt +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_CMD_001.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_agent_profiles.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_agent_runner_ready.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_ai_client.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_ai_intelligence.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_auditor.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_channel.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_chat_diff_decision.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_chat_runner_openai_compat.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_chat_stdin_protocol.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_cli.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_cli_workflows_history_drive.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_compliance.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_compressor.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_dispatch.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_e2e_nexus.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_endpoints_cli.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_endpoints_store.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_epistemic.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_fallback_chain.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_importer.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_integrations.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_integrations_codity.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_intelligence.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_issue_reporter.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_mcp_client.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_new_modules.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_nexus.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_permissions.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_phase1_4_new.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_phase34_completion.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_rate_limits.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_req_248_262.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_scaffolder.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_skill_marketplace.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_skills_mcp.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_smoke.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_suggester.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_tools.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_validator.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_vcs.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_warp_parity.py +0 -0
- {specsmith-0.11.5.dev452 → specsmith-0.11.5.dev453}/tests/test_warp_parity_followup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: specsmith
|
|
3
|
-
Version: 0.11.5.
|
|
3
|
+
Version: 0.11.5.dev453
|
|
4
4
|
Summary: Applied Epistemic Engineering toolkit — AEE agent sessions, execution profiles, FPGA/HDL governance, tool installer, 50+ CLI commands.
|
|
5
5
|
Author: BitConcepts
|
|
6
6
|
License-Expression: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specsmith"
|
|
7
|
-
version = "0.11.5.
|
|
7
|
+
version = "0.11.5.dev453"
|
|
8
8
|
description = "Applied Epistemic Engineering toolkit — AEE agent sessions, execution profiles, FPGA/HDL governance, tool installer, 50+ CLI commands."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -34,6 +34,24 @@ from specsmith.agent.verifier import (
|
|
|
34
34
|
|
|
35
35
|
DEFAULT_OLLAMA_HOST = "http://127.0.0.1:11434"
|
|
36
36
|
DEFAULT_OLLAMA_MODEL = os.environ.get("SPECSMITH_OLLAMA_MODEL", "qwen2.5:7b")
|
|
37
|
+
|
|
38
|
+
# Ordered preference list used by _pick_ollama_model() when the default
|
|
39
|
+
# model is not installed. Lighter / faster models first so the REPL
|
|
40
|
+
# stays responsive on developer hardware.
|
|
41
|
+
_OLLAMA_MODEL_PREFERENCE = [
|
|
42
|
+
"qwen2.5:7b",
|
|
43
|
+
"qwen2.5-coder:7b-instruct",
|
|
44
|
+
"qwen3:8b",
|
|
45
|
+
"mistral:7b-instruct-q4_0",
|
|
46
|
+
"llama3:8b-instruct-q4_K_M",
|
|
47
|
+
"qwen2.5:14b",
|
|
48
|
+
"qwen2.5-coder:14b",
|
|
49
|
+
"qwen3:14b",
|
|
50
|
+
"mistral-nemo:12b",
|
|
51
|
+
"phi4:14b-q4_K_M",
|
|
52
|
+
"deepseek-r1:14b",
|
|
53
|
+
"qwen3:30b-a3b",
|
|
54
|
+
]
|
|
37
55
|
SYSTEM_PROMPT = (
|
|
38
56
|
"You are Nexus, the local-first agentic developer assistant inside "
|
|
39
57
|
"Specsmith. Always end your response with the canonical contract:\n"
|
|
@@ -239,6 +257,40 @@ def run_single_prompt(prompt: str, *, max_tokens: int = 500) -> str | None: # n
|
|
|
239
257
|
# ---------------------------------------------------------------------------
|
|
240
258
|
|
|
241
259
|
|
|
260
|
+
def _ollama_alive(host: str) -> bool:
|
|
261
|
+
try:
|
|
262
|
+
with urlopen(f"{host}/api/tags", timeout=2): # noqa: S310
|
|
263
|
+
return True
|
|
264
|
+
except (URLError, TimeoutError, OSError):
|
|
265
|
+
return False
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _pick_ollama_model(host: str) -> str:
|
|
269
|
+
"""Return the best available Ollama model for this machine.
|
|
270
|
+
|
|
271
|
+
Checks the installed model list from ``/api/tags`` and walks
|
|
272
|
+
``_OLLAMA_MODEL_PREFERENCE`` to find the first match. Falls back
|
|
273
|
+
to ``DEFAULT_OLLAMA_MODEL`` when the API is unreachable or the list
|
|
274
|
+
is empty. An explicit ``SPECSMITH_OLLAMA_MODEL`` env var always wins.
|
|
275
|
+
"""
|
|
276
|
+
env_override = os.environ.get("SPECSMITH_OLLAMA_MODEL", "").strip()
|
|
277
|
+
if env_override:
|
|
278
|
+
return env_override
|
|
279
|
+
try:
|
|
280
|
+
with urlopen(f"{host}/api/tags", timeout=2) as resp: # noqa: S310
|
|
281
|
+
data = json.loads(resp.read())
|
|
282
|
+
installed = {m["name"] for m in data.get("models", []) if m.get("name")}
|
|
283
|
+
for candidate in _OLLAMA_MODEL_PREFERENCE:
|
|
284
|
+
if candidate in installed:
|
|
285
|
+
return candidate
|
|
286
|
+
# None of the preferred models found — use whatever is first
|
|
287
|
+
if installed:
|
|
288
|
+
return sorted(installed)[0]
|
|
289
|
+
except Exception: # noqa: BLE001 — best-effort; fall through to default
|
|
290
|
+
pass
|
|
291
|
+
return DEFAULT_OLLAMA_MODEL
|
|
292
|
+
|
|
293
|
+
|
|
242
294
|
def _run_ollama(
|
|
243
295
|
messages: list[dict[str, str]],
|
|
244
296
|
emitter: EventEmitter,
|
|
@@ -246,12 +298,13 @@ def _run_ollama(
|
|
|
246
298
|
) -> tuple[str | None, _UsageDelta]:
|
|
247
299
|
"""Stream from a local Ollama daemon using only stdlib."""
|
|
248
300
|
host = os.environ.get("OLLAMA_HOST", DEFAULT_OLLAMA_HOST).rstrip("/")
|
|
249
|
-
model = os.environ.get("SPECSMITH_OLLAMA_MODEL", DEFAULT_OLLAMA_MODEL)
|
|
250
301
|
usage = _UsageDelta()
|
|
251
302
|
|
|
252
303
|
if not _ollama_alive(host):
|
|
253
304
|
return None, usage
|
|
254
305
|
|
|
306
|
+
model = _pick_ollama_model(host)
|
|
307
|
+
|
|
255
308
|
payload = json.dumps({"model": model, "messages": messages, "stream": True}).encode("utf-8")
|
|
256
309
|
req = Request( # noqa: S310 - URL is a hardcoded localhost default
|
|
257
310
|
f"{host}/api/chat",
|
|
@@ -284,14 +337,6 @@ def _run_ollama(
|
|
|
284
337
|
return ("".join(pieces) if pieces else None), usage
|
|
285
338
|
|
|
286
339
|
|
|
287
|
-
def _ollama_alive(host: str) -> bool:
|
|
288
|
-
try:
|
|
289
|
-
with urlopen(f"{host}/api/tags", timeout=2): # noqa: S310
|
|
290
|
-
return True
|
|
291
|
-
except (URLError, TimeoutError, OSError):
|
|
292
|
-
return False
|
|
293
|
-
|
|
294
|
-
|
|
295
340
|
def _run_anthropic(
|
|
296
341
|
messages: list[dict[str, str]],
|
|
297
342
|
emitter: EventEmitter,
|
|
@@ -231,4 +231,24 @@ class EventEmitter:
|
|
|
231
231
|
)
|
|
232
232
|
|
|
233
233
|
|
|
234
|
-
|
|
234
|
+
class PlainTextEmitter(EventEmitter):
|
|
235
|
+
"""Human-readable variant of EventEmitter for interactive terminal sessions.
|
|
236
|
+
|
|
237
|
+
Writes LLM token chunks directly to the stream (no JSON wrapping) so
|
|
238
|
+
``specsmith run`` without ``--json-events`` produces readable output.
|
|
239
|
+
All non-token protocol events are silently dropped — system messages
|
|
240
|
+
are handled separately by ``AgentRunner._default_emit_event``.
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
def token(self, block_id: str, text: str) -> None: # noqa: ARG002
|
|
244
|
+
self.stream.write(text)
|
|
245
|
+
with contextlib.suppress(Exception):
|
|
246
|
+
self.stream.flush()
|
|
247
|
+
|
|
248
|
+
def emit(self, event: dict[str, Any]) -> None:
|
|
249
|
+
# Drop JSONL protocol frames in plain-text mode; token() handles
|
|
250
|
+
# the only output that matters for interactive sessions.
|
|
251
|
+
pass
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
__all__ = ["EventEmitter", "PlainTextEmitter"]
|
|
@@ -27,13 +27,160 @@ from pathlib import Path
|
|
|
27
27
|
from typing import Any
|
|
28
28
|
|
|
29
29
|
from specsmith.agent.core import AgentState, ModelTier
|
|
30
|
-
from specsmith.agent.events import EventEmitter
|
|
30
|
+
from specsmith.agent.events import EventEmitter, PlainTextEmitter
|
|
31
31
|
|
|
32
32
|
# These imports are kept lazy in the public API so that a busted optional
|
|
33
33
|
# dependency (e.g. ``ag2``) doesn't keep the bridge from emitting ``ready``.
|
|
34
34
|
# The import itself happens on the first call that actually needs the
|
|
35
35
|
# orchestrator group chat.
|
|
36
|
-
__all__ = ["AgentRunner", "_capabilities"]
|
|
36
|
+
__all__ = ["AgentRunner", "ProviderStatus", "check_providers", "_capabilities"]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
# Provider health check
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
from dataclasses import dataclass # noqa: E402 — after __all__
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class ProviderStatus:
|
|
49
|
+
"""Health snapshot for a single LLM provider."""
|
|
50
|
+
|
|
51
|
+
name: str
|
|
52
|
+
available: bool
|
|
53
|
+
model: str = "" # resolved model name (empty when unavailable)
|
|
54
|
+
note: str = "" # human-readable reason (error or extra context)
|
|
55
|
+
model_count: int = 0 # number of installed models (Ollama only)
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def icon(self) -> str:
|
|
59
|
+
return "\u2713" if self.available else "\u2717"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def check_providers() -> list[ProviderStatus]:
|
|
63
|
+
"""Probe every supported LLM provider and return a status list.
|
|
64
|
+
|
|
65
|
+
Safe to call at any time — never raises, never blocks for more than
|
|
66
|
+
a couple of seconds. Used by ``_print_banner`` and ``specsmith run
|
|
67
|
+
--check``.
|
|
68
|
+
"""
|
|
69
|
+
import importlib
|
|
70
|
+
import os
|
|
71
|
+
|
|
72
|
+
from specsmith.agent.chat_runner import (
|
|
73
|
+
_OLLAMA_MODEL_PREFERENCE,
|
|
74
|
+
DEFAULT_OLLAMA_HOST,
|
|
75
|
+
DEFAULT_OLLAMA_MODEL,
|
|
76
|
+
_ollama_alive,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
results: list[ProviderStatus] = []
|
|
80
|
+
|
|
81
|
+
# ── Ollama ───────────────────────────────────────────────────────────
|
|
82
|
+
host = os.environ.get("OLLAMA_HOST", DEFAULT_OLLAMA_HOST).rstrip("/")
|
|
83
|
+
if _ollama_alive(host):
|
|
84
|
+
try:
|
|
85
|
+
import json
|
|
86
|
+
from urllib.request import urlopen
|
|
87
|
+
|
|
88
|
+
with urlopen(f"{host}/api/tags", timeout=2) as resp: # noqa: S310
|
|
89
|
+
data = json.loads(resp.read())
|
|
90
|
+
installed = [m["name"] for m in data.get("models", []) if m.get("name")]
|
|
91
|
+
installed_set = set(installed)
|
|
92
|
+
model = os.environ.get("SPECSMITH_OLLAMA_MODEL", "").strip()
|
|
93
|
+
source = "env"
|
|
94
|
+
if not model:
|
|
95
|
+
source = "auto"
|
|
96
|
+
for candidate in _OLLAMA_MODEL_PREFERENCE:
|
|
97
|
+
if candidate in installed_set:
|
|
98
|
+
model = candidate
|
|
99
|
+
break
|
|
100
|
+
if not model and installed:
|
|
101
|
+
model = sorted(installed_set)[0]
|
|
102
|
+
if not model:
|
|
103
|
+
model = DEFAULT_OLLAMA_MODEL
|
|
104
|
+
note = f"{source}, {len(installed)} installed"
|
|
105
|
+
results.append(
|
|
106
|
+
ProviderStatus(
|
|
107
|
+
name="ollama",
|
|
108
|
+
available=True,
|
|
109
|
+
model=model,
|
|
110
|
+
note=note,
|
|
111
|
+
model_count=len(installed),
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
except Exception as exc: # noqa: BLE001
|
|
115
|
+
results.append(
|
|
116
|
+
ProviderStatus(name="ollama", available=False, note=f"error reading tags: {exc}")
|
|
117
|
+
)
|
|
118
|
+
else:
|
|
119
|
+
results.append(
|
|
120
|
+
ProviderStatus(
|
|
121
|
+
name="ollama",
|
|
122
|
+
available=False,
|
|
123
|
+
note=f"not running at {host} — start with: ollama serve",
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# ── Anthropic ────────────────────────────────────────────────────────
|
|
128
|
+
key = os.environ.get("ANTHROPIC_API_KEY", "")
|
|
129
|
+
if not key:
|
|
130
|
+
results.append(
|
|
131
|
+
ProviderStatus(name="anthropic", available=False, note="no ANTHROPIC_API_KEY")
|
|
132
|
+
)
|
|
133
|
+
elif importlib.util.find_spec("anthropic") is None:
|
|
134
|
+
results.append(
|
|
135
|
+
ProviderStatus(
|
|
136
|
+
name="anthropic",
|
|
137
|
+
available=False,
|
|
138
|
+
note="key set but SDK missing — run: pipx inject specsmith anthropic",
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
else:
|
|
142
|
+
model = os.environ.get("ANTHROPIC_MODEL", "claude-haiku-4-5")
|
|
143
|
+
results.append(
|
|
144
|
+
ProviderStatus(name="anthropic", available=True, model=model, note="key configured")
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# ── OpenAI ───────────────────────────────────────────────────────────
|
|
148
|
+
key = os.environ.get("OPENAI_API_KEY", "")
|
|
149
|
+
if not key:
|
|
150
|
+
results.append(ProviderStatus(name="openai", available=False, note="no OPENAI_API_KEY"))
|
|
151
|
+
elif importlib.util.find_spec("openai") is None:
|
|
152
|
+
results.append(
|
|
153
|
+
ProviderStatus(
|
|
154
|
+
name="openai",
|
|
155
|
+
available=False,
|
|
156
|
+
note="key set but SDK missing — run: pipx inject specsmith openai",
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
else:
|
|
160
|
+
model = os.environ.get("OPENAI_MODEL", "gpt-4o-mini")
|
|
161
|
+
results.append(
|
|
162
|
+
ProviderStatus(name="openai", available=True, model=model, note="key configured")
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# ── Gemini ───────────────────────────────────────────────────────────
|
|
166
|
+
key = os.environ.get("GOOGLE_API_KEY", "")
|
|
167
|
+
if not key:
|
|
168
|
+
results.append(ProviderStatus(name="gemini", available=False, note="no GOOGLE_API_KEY"))
|
|
169
|
+
elif importlib.util.find_spec("google.genai") is None:
|
|
170
|
+
results.append(
|
|
171
|
+
ProviderStatus(
|
|
172
|
+
name="gemini",
|
|
173
|
+
available=False,
|
|
174
|
+
note="key set but SDK missing — run: pipx inject specsmith google-genai",
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
else:
|
|
178
|
+
model = os.environ.get("GEMINI_MODEL", "gemini-2.5-flash")
|
|
179
|
+
results.append(
|
|
180
|
+
ProviderStatus(name="gemini", available=True, model=model, note="key configured")
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
return results
|
|
37
184
|
|
|
38
185
|
|
|
39
186
|
# ---------------------------------------------------------------------------
|
|
@@ -156,7 +303,14 @@ class AgentRunner:
|
|
|
156
303
|
self.endpoint_id = (endpoint_id or "").strip() or None
|
|
157
304
|
self.profile_id = (profile_id or "").strip() or None
|
|
158
305
|
|
|
159
|
-
|
|
306
|
+
# Use a plain-text emitter in interactive mode so LLM tokens render
|
|
307
|
+
# as readable prose instead of raw JSONL frames on the terminal.
|
|
308
|
+
if emitter is not None:
|
|
309
|
+
self._emitter = emitter
|
|
310
|
+
elif json_events:
|
|
311
|
+
self._emitter = EventEmitter(stream=sys.stdout)
|
|
312
|
+
else:
|
|
313
|
+
self._emitter = PlainTextEmitter(stream=sys.stdout)
|
|
160
314
|
self._state = AgentState(
|
|
161
315
|
provider_name=self.provider_name,
|
|
162
316
|
model_name=self.model,
|
|
@@ -187,37 +341,58 @@ class AgentRunner:
|
|
|
187
341
|
# ── Public lifecycle ───────────────────────────────────────────────
|
|
188
342
|
|
|
189
343
|
def _print_banner(self) -> None:
|
|
190
|
-
"""Emit the ``ready`` handshake (or print a plain banner
|
|
344
|
+
"""Emit the ``ready`` handshake (or print a plain banner.
|
|
191
345
|
|
|
192
346
|
Called exactly once at process start. The bridge waits up to 20 s
|
|
193
|
-
for this frame; when ``json_events`` is False we
|
|
194
|
-
|
|
195
|
-
|
|
347
|
+
for this frame; when ``json_events`` is False we print a human-
|
|
348
|
+
readable provider status table so the user knows exactly what will
|
|
349
|
+
respond to their commands before they type anything.
|
|
196
350
|
"""
|
|
197
351
|
version = self._package_version()
|
|
198
352
|
if self.json_events:
|
|
353
|
+
# When a model/provider was explicitly set (e.g. via CLI flag or test),
|
|
354
|
+
# use those. Only probe check_providers() when no model was pinned.
|
|
355
|
+
if self.model:
|
|
356
|
+
provider_for_ready = self.provider_name
|
|
357
|
+
model_for_ready = self.model
|
|
358
|
+
else:
|
|
359
|
+
statuses = check_providers()
|
|
360
|
+
active = next((s for s in statuses if s.available), None)
|
|
361
|
+
provider_for_ready = active.name if active else self.provider_name
|
|
362
|
+
model_for_ready = active.model if active else ""
|
|
199
363
|
self._emitter.ready(
|
|
200
364
|
agent="nexus",
|
|
201
365
|
version=version,
|
|
202
366
|
project_dir=self.project_dir,
|
|
203
|
-
provider=
|
|
204
|
-
model=
|
|
367
|
+
provider=provider_for_ready,
|
|
368
|
+
model=model_for_ready,
|
|
205
369
|
profile_id=self.profile_id or "",
|
|
206
370
|
capabilities=_capabilities(),
|
|
207
371
|
endpoint_id=self.endpoint_id or "",
|
|
208
372
|
)
|
|
209
373
|
else:
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
f"
|
|
214
|
-
f"
|
|
215
|
-
f"
|
|
216
|
-
|
|
217
|
-
"
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
374
|
+
statuses = check_providers()
|
|
375
|
+
active_count = sum(1 for s in statuses if s.available)
|
|
376
|
+
lines = [
|
|
377
|
+
f"Nexus {version} — specsmith run",
|
|
378
|
+
f" project : {self.project_dir}",
|
|
379
|
+
f" profile : {self.profile_id or '(default)'}",
|
|
380
|
+
"",
|
|
381
|
+
" LLM providers:",
|
|
382
|
+
]
|
|
383
|
+
for s in statuses:
|
|
384
|
+
if s.available:
|
|
385
|
+
lines.append(
|
|
386
|
+
f" {s.icon} {s.name:<10} ✓ ready model: {s.model} ({s.note})"
|
|
387
|
+
)
|
|
388
|
+
else:
|
|
389
|
+
lines.append(f" {s.icon} {s.name:<10} ✗ {s.note}")
|
|
390
|
+
lines.append("")
|
|
391
|
+
if active_count == 0:
|
|
392
|
+
lines.append(" ⚠ No provider available — commands will return no response.")
|
|
393
|
+
else:
|
|
394
|
+
lines.append(" Type plain English, or use /plan /ask /fix /test /commit /pr /exit")
|
|
395
|
+
print("\n".join(lines), flush=True)
|
|
221
396
|
|
|
222
397
|
def run_interactive(self) -> None:
|
|
223
398
|
"""Read stdin lines and dispatch each to :meth:`_handle_command`."""
|
|
@@ -230,6 +405,10 @@ class AgentRunner:
|
|
|
230
405
|
if line.strip().lower() in {"/exit", "/quit"}:
|
|
231
406
|
break
|
|
232
407
|
self._handle_command(line)
|
|
408
|
+
# Ensure streamed tokens are followed by a newline so the
|
|
409
|
+
# next input prompt doesn't bleed onto the response line.
|
|
410
|
+
if not self.json_events:
|
|
411
|
+
print(flush=True)
|
|
233
412
|
if self.json_events:
|
|
234
413
|
self._emit_event(type="turn_done")
|
|
235
414
|
if self._hard_stop:
|
|
@@ -316,7 +495,12 @@ class AgentRunner:
|
|
|
316
495
|
|
|
317
496
|
block_id = self._next_block_id()
|
|
318
497
|
try:
|
|
319
|
-
from specsmith.agent.chat_runner import
|
|
498
|
+
from specsmith.agent.chat_runner import (
|
|
499
|
+
DEFAULT_OLLAMA_HOST,
|
|
500
|
+
_ollama_alive,
|
|
501
|
+
_pick_ollama_model,
|
|
502
|
+
run_chat,
|
|
503
|
+
)
|
|
320
504
|
|
|
321
505
|
result = run_chat(
|
|
322
506
|
full_prompt,
|
|
@@ -336,6 +520,28 @@ class AgentRunner:
|
|
|
336
520
|
)
|
|
337
521
|
return None
|
|
338
522
|
|
|
523
|
+
if result is None:
|
|
524
|
+
# All providers failed — give the user an actionable explanation
|
|
525
|
+
# rather than silent emptiness.
|
|
526
|
+
import os as _os
|
|
527
|
+
|
|
528
|
+
host = _os.environ.get("OLLAMA_HOST", DEFAULT_OLLAMA_HOST).rstrip("/")
|
|
529
|
+
if _ollama_alive(host):
|
|
530
|
+
model = _pick_ollama_model(host)
|
|
531
|
+
hint = (
|
|
532
|
+
f"Ollama is running but returned no response "
|
|
533
|
+
f"(model: {model}). "
|
|
534
|
+
"Try: ollama run " + model
|
|
535
|
+
)
|
|
536
|
+
else:
|
|
537
|
+
hint = (
|
|
538
|
+
"No LLM provider available. Options:\n"
|
|
539
|
+
" • Start Ollama: ollama serve\n"
|
|
540
|
+
" • Set ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_API_KEY"
|
|
541
|
+
)
|
|
542
|
+
self._emit_event(type="system", message=hint)
|
|
543
|
+
return None
|
|
544
|
+
|
|
339
545
|
# Aggregate metrics into the session state (C1).
|
|
340
546
|
# ``run_chat`` now reports tokens_in / tokens_out / cost_usd off the
|
|
341
547
|
# provider response (Ollama prompt_eval_count + eval_count, OpenAI
|
|
@@ -3661,6 +3661,13 @@ def abort_cmd(pid: int | None, abort_all_flag: bool, project_dir: str) -> None:
|
|
|
3661
3661
|
|
|
3662
3662
|
@main.command(name="run")
|
|
3663
3663
|
@click.option("--project-dir", type=click.Path(exists=True), default=".")
|
|
3664
|
+
@click.option(
|
|
3665
|
+
"--check",
|
|
3666
|
+
"check_only",
|
|
3667
|
+
is_flag=True,
|
|
3668
|
+
default=False,
|
|
3669
|
+
help="Check provider availability and exit (no REPL started).",
|
|
3670
|
+
)
|
|
3664
3671
|
@click.option(
|
|
3665
3672
|
"--task",
|
|
3666
3673
|
"task",
|
|
@@ -3718,6 +3725,7 @@ def abort_cmd(pid: int | None, abort_all_flag: bool, project_dir: str) -> None:
|
|
|
3718
3725
|
)
|
|
3719
3726
|
def run_cmd(
|
|
3720
3727
|
project_dir: str,
|
|
3728
|
+
check_only: bool,
|
|
3721
3729
|
task: str,
|
|
3722
3730
|
provider_name: str | None,
|
|
3723
3731
|
model: str | None,
|
|
@@ -3737,10 +3745,39 @@ def run_cmd(
|
|
|
3737
3745
|
Ollama running → local LLMs (zero config)
|
|
3738
3746
|
SPECSMITH_PROVIDER=<name> → explicit override
|
|
3739
3747
|
|
|
3748
|
+
Use --check to validate provider configuration without starting a session.
|
|
3749
|
+
|
|
3740
3750
|
Install a provider:
|
|
3741
3751
|
pipx inject specsmith anthropic # Claude
|
|
3742
3752
|
pipx inject specsmith openai # GPT/O-series
|
|
3743
3753
|
"""
|
|
3754
|
+
from specsmith.agent.runner import check_providers
|
|
3755
|
+
|
|
3756
|
+
if check_only:
|
|
3757
|
+
statuses = check_providers()
|
|
3758
|
+
any_ok = any(s.available for s in statuses)
|
|
3759
|
+
console.print("[bold]specsmith run --check[/bold]\n")
|
|
3760
|
+
for s in statuses:
|
|
3761
|
+
if s.available:
|
|
3762
|
+
console.print(
|
|
3763
|
+
f" [green]\u2713[/green] {s.name:<10} "
|
|
3764
|
+
f"model: [bold]{s.model}[/bold] ({s.note})"
|
|
3765
|
+
)
|
|
3766
|
+
else:
|
|
3767
|
+
console.print(f" [red]\u2717[/red] {s.name:<10} {s.note}")
|
|
3768
|
+
console.print()
|
|
3769
|
+
if any_ok:
|
|
3770
|
+
active = next(s for s in statuses if s.available)
|
|
3771
|
+
console.print(
|
|
3772
|
+
f"[bold green]Ready.[/bold green] Primary provider: {active.name} / {active.model}"
|
|
3773
|
+
)
|
|
3774
|
+
else:
|
|
3775
|
+
console.print(
|
|
3776
|
+
"[bold red]No provider available.[/bold red] Start Ollama or set an API key."
|
|
3777
|
+
)
|
|
3778
|
+
raise SystemExit(1)
|
|
3779
|
+
return
|
|
3780
|
+
|
|
3744
3781
|
from specsmith.agent.core import ModelTier
|
|
3745
3782
|
from specsmith.agent.runner import AgentRunner
|
|
3746
3783
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: specsmith
|
|
3
|
-
Version: 0.11.5.
|
|
3
|
+
Version: 0.11.5.dev453
|
|
4
4
|
Summary: Applied Epistemic Engineering toolkit — AEE agent sessions, execution profiles, FPGA/HDL governance, tool installer, 50+ CLI commands.
|
|
5
5
|
Author: BitConcepts
|
|
6
6
|
License-Expression: MIT
|
|
@@ -239,6 +239,7 @@ src/specsmith/vcs/github.py
|
|
|
239
239
|
src/specsmith/vcs/gitlab.py
|
|
240
240
|
tests/test_CMD_001.py
|
|
241
241
|
tests/test_agent_profiles.py
|
|
242
|
+
tests/test_agent_run_feedback.py
|
|
242
243
|
tests/test_agent_runner_ready.py
|
|
243
244
|
tests/test_ai_client.py
|
|
244
245
|
tests/test_ai_intelligence.py
|