specsmith 0.8.0.dev237__tar.gz → 0.10.0.dev238__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.8.0.dev237/src/specsmith.egg-info → specsmith-0.10.0.dev238}/PKG-INFO +1 -1
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/pyproject.toml +4 -1
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/__init__.py +1 -1
- specsmith-0.10.0.dev238/src/specsmith/agent/core.py +98 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/events.py +58 -0
- specsmith-0.10.0.dev238/src/specsmith/agent/fallback.py +142 -0
- specsmith-0.10.0.dev238/src/specsmith/agent/profiles.py +569 -0
- specsmith-0.10.0.dev238/src/specsmith/agent/runner.py +399 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/cli.py +511 -143
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/vcs_commands.py +1 -1
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238/src/specsmith.egg-info}/PKG-INFO +1 -1
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith.egg-info/SOURCES.txt +7 -1
- specsmith-0.10.0.dev238/tests/test_agent_profiles.py +70 -0
- specsmith-0.10.0.dev238/tests/test_agent_runner_ready.py +75 -0
- specsmith-0.10.0.dev238/tests/test_fallback_chain.py +343 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_phase34_completion.py +4 -36
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_warp_parity.py +4 -115
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_warp_parity_followup.py +0 -95
- specsmith-0.8.0.dev237/src/specsmith/cloud_serve.py +0 -150
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/LICENSE +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/README.md +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/setup.cfg +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/epistemic/__init__.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/epistemic/belief.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/epistemic/certainty.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/epistemic/failure_graph.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/epistemic/py.typed +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/epistemic/recovery.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/epistemic/session.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/epistemic/stress_tester.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/epistemic/trace.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/__main__.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/__init__.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/broker.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/chat_runner.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/cleanup.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/endpoints.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/indexer.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/mcp.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/memory.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/orchestrator.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/repl.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/router.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/rules.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/safety.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/suggester.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/tools.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/verifier.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/voice.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/architect.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/auditor.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/auth.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/block_export.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/commands/__init__.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/compressor.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/config.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/console_utils.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/credit_analyzer.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/credits.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/differ.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/doctor.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/drive.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/epistemic/__init__.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/epistemic/belief.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/epistemic/certainty.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/epistemic/failure_graph.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/epistemic/recovery.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/epistemic/stress_tester.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/executor.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/exporter.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/__init__.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/app.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/main_window.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/session_tab.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/theme.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/widgets/__init__.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/widgets/chat_view.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/widgets/input_bar.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/widgets/provider_bar.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/widgets/token_meter.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/widgets/tool_panel.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/widgets/update_checker.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/worker.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/history_search.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/importer.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/integrations/__init__.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/integrations/agent_skill.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/integrations/aider.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/integrations/base.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/integrations/claude_code.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/integrations/copilot.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/integrations/cursor.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/integrations/gemini.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/integrations/windsurf.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/languages.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/ledger.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/patent.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/phase.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/plugins.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/profiles.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/rate_limits.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/releaser.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/requirements.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/requirements_parser.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/retrieval.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/scaffolder.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/serve.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/session.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/skills.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/agents.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/community/bug_report.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/community/code_of_conduct.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/community/contributing.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/community/feature_request.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/community/license-Apache-2.0.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/community/license-MIT.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/community/pull_request_template.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/community/security.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/docs/architecture.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/docs/mkdocs.yml.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/docs/readthedocs.yaml.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/docs/requirements.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/docs/test-spec.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/editorconfig.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/gitattributes.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/gitignore.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/go/go.mod.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/go/main.go.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/belief-registry.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/context-budget.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/drift-metrics.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/epistemic-axioms.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/failure-modes.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/lifecycle.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/roles.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/rules.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/session-protocol.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/uncertainty-map.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/verification.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/js/package.json.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/ledger.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/python/cli.py.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/python/init.py.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/python/pyproject.toml.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/readme.md.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/rust/Cargo.toml.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/rust/main.rs.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/scripts/exec.cmd.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/scripts/exec.sh.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/scripts/run.cmd.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/scripts/run.sh.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/scripts/setup.cmd.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/scripts/setup.sh.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/workflows/release.yml.j2 +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/tool_installer.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/toolrules.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/tools.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/trace.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/updater.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/upgrader.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/validator.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/vcs/__init__.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/vcs/base.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/vcs/bitbucket.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/vcs/github.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/vcs/gitlab.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/wireframes.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/workspace.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith.egg-info/dependency_links.txt +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith.egg-info/entry_points.txt +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith.egg-info/requires.txt +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith.egg-info/top_level.txt +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_CMD_001.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_auditor.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_chat_diff_decision.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_chat_runner_openai_compat.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_chat_stdin_protocol.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_cli.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_cli_workflows_history_drive.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_compressor.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_e2e_nexus.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_endpoints_cli.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_endpoints_store.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_epistemic.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_importer.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_integrations.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_mcp_client.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_nexus.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_phase1_4_new.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_rate_limits.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_scaffolder.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_skill_marketplace.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_smoke.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_suggester.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_tools.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_validator.py +0 -0
- {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_vcs.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: specsmith
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0.dev238
|
|
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.
|
|
7
|
+
version = "0.10.0.dev238"
|
|
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"
|
|
@@ -171,6 +171,9 @@ module = [
|
|
|
171
171
|
"specsmith.importer",
|
|
172
172
|
"specsmith.agent.providers.gemini",
|
|
173
173
|
"specsmith.agent.runner",
|
|
174
|
+
"specsmith.agent.profiles",
|
|
175
|
+
"specsmith.agent.fallback",
|
|
176
|
+
"specsmith.agent.core",
|
|
174
177
|
"specsmith.agent.cleanup",
|
|
175
178
|
"specsmith.agent.orchestrator",
|
|
176
179
|
"specsmith.agent.repl",
|
|
@@ -8,4 +8,4 @@ from importlib.metadata import version as _pkg_version
|
|
|
8
8
|
try:
|
|
9
9
|
__version__: str = _pkg_version("specsmith")
|
|
10
10
|
except PackageNotFoundError: # running from source without install
|
|
11
|
-
__version__ = "0.
|
|
11
|
+
__version__ = "0.10.0" # fallback: keep in sync with pyproject.toml
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 BitConcepts, LLC. All rights reserved.
|
|
3
|
+
"""Shared agent runtime primitives (REQ-145).
|
|
4
|
+
|
|
5
|
+
Hosts low-level enums and dataclasses that span :mod:`specsmith.agent.runner`,
|
|
6
|
+
:mod:`specsmith.serve`, :mod:`specsmith.agent.profiles`, and
|
|
7
|
+
:mod:`specsmith.agent.fallback` without forcing them to import each other.
|
|
8
|
+
|
|
9
|
+
The historical ``cli.py`` referenced ``ModelTier`` from this module before
|
|
10
|
+
it existed in the source tree (the file was lost in an earlier refactor),
|
|
11
|
+
which produced an ``ImportError`` the moment ``specsmith run`` was
|
|
12
|
+
invoked. Restoring the symbol here is the prerequisite for the bridge
|
|
13
|
+
``ready`` event handshake to land before the VS Code extension's 20 s
|
|
14
|
+
startup timeout fires.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import enum
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ModelTier(str, enum.Enum):
|
|
25
|
+
"""Capability tier for an LLM call.
|
|
26
|
+
|
|
27
|
+
Ordered cheapest → most capable so that a fallback chain can iterate
|
|
28
|
+
in declaration order without external metadata.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
FAST = "fast"
|
|
32
|
+
BALANCED = "balanced"
|
|
33
|
+
POWERFUL = "powerful"
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def parse(
|
|
37
|
+
cls,
|
|
38
|
+
value: str | ModelTier | None,
|
|
39
|
+
default: ModelTier | None = None,
|
|
40
|
+
) -> ModelTier:
|
|
41
|
+
"""Tolerant parser used by CLI option handlers."""
|
|
42
|
+
if value is None or value == "":
|
|
43
|
+
return default or cls.BALANCED
|
|
44
|
+
if isinstance(value, cls):
|
|
45
|
+
return value
|
|
46
|
+
try:
|
|
47
|
+
return cls(str(value).strip().lower())
|
|
48
|
+
except ValueError:
|
|
49
|
+
return default or cls.BALANCED
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class AgentState:
|
|
54
|
+
"""Mutable per-session metrics surfaced via ``specsmith serve``'s
|
|
55
|
+
``GET /api/status`` endpoint and the VS Code TokenMeter chip.
|
|
56
|
+
|
|
57
|
+
Field names mirror what :class:`specsmith.serve._AgentThread` reads off
|
|
58
|
+
``runner._state``; do not rename without updating that consumer.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
provider_name: str = ""
|
|
62
|
+
model_name: str = ""
|
|
63
|
+
profile_id: str = ""
|
|
64
|
+
session_tokens: int = 0
|
|
65
|
+
tokens_in: int = 0
|
|
66
|
+
tokens_out: int = 0
|
|
67
|
+
total_cost_usd: float = 0.0
|
|
68
|
+
tool_calls_made: int = 0
|
|
69
|
+
elapsed_minutes: float = 0.0
|
|
70
|
+
by_profile: dict[str, dict[str, Any]] = field(default_factory=dict)
|
|
71
|
+
|
|
72
|
+
def credit(
|
|
73
|
+
self,
|
|
74
|
+
*,
|
|
75
|
+
profile_id: str,
|
|
76
|
+
tokens_in: int = 0,
|
|
77
|
+
tokens_out: int = 0,
|
|
78
|
+
cost_usd: float = 0.0,
|
|
79
|
+
tool_calls: int = 0,
|
|
80
|
+
) -> None:
|
|
81
|
+
"""Aggregate one turn's metrics into the running totals."""
|
|
82
|
+
self.tokens_in += int(tokens_in)
|
|
83
|
+
self.tokens_out += int(tokens_out)
|
|
84
|
+
self.session_tokens = self.tokens_in + self.tokens_out
|
|
85
|
+
self.total_cost_usd += float(cost_usd)
|
|
86
|
+
self.tool_calls_made += int(tool_calls)
|
|
87
|
+
bucket = self.by_profile.setdefault(
|
|
88
|
+
profile_id or "(default)",
|
|
89
|
+
{"tokens_in": 0, "tokens_out": 0, "cost_usd": 0.0, "tool_calls": 0, "turns": 0},
|
|
90
|
+
)
|
|
91
|
+
bucket["tokens_in"] += int(tokens_in)
|
|
92
|
+
bucket["tokens_out"] += int(tokens_out)
|
|
93
|
+
bucket["cost_usd"] = round(bucket["cost_usd"] + float(cost_usd), 6)
|
|
94
|
+
bucket["tool_calls"] += int(tool_calls)
|
|
95
|
+
bucket["turns"] += 1
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
__all__ = ["AgentState", "ModelTier"]
|
|
@@ -19,6 +19,9 @@ Event kinds
|
|
|
19
19
|
* ``plan_step`` - status transition for a step in the active plan
|
|
20
20
|
block (REQ-114).
|
|
21
21
|
* ``task_complete`` - final block; carries final summary + profile.
|
|
22
|
+
* ``ready`` - emitted exactly once at process start (REQ-145);
|
|
23
|
+
the VS Code bridge waits up to 20 s for this
|
|
24
|
+
frame before declaring the agent unresponsive.
|
|
22
25
|
"""
|
|
23
26
|
|
|
24
27
|
from __future__ import annotations
|
|
@@ -58,6 +61,61 @@ class EventEmitter:
|
|
|
58
61
|
with contextlib.suppress(Exception):
|
|
59
62
|
self.stream.flush()
|
|
60
63
|
|
|
64
|
+
# ── Lifecycle helpers ────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
def ready(
|
|
67
|
+
self,
|
|
68
|
+
*,
|
|
69
|
+
agent: str = "nexus",
|
|
70
|
+
version: str = "",
|
|
71
|
+
project_dir: str = "",
|
|
72
|
+
provider: str = "",
|
|
73
|
+
model: str = "",
|
|
74
|
+
profile_id: str = "",
|
|
75
|
+
capabilities: list[str] | None = None,
|
|
76
|
+
**extra: Any,
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Emit the bridge handshake frame (REQ-145).
|
|
79
|
+
|
|
80
|
+
The VS Code extension's :class:`SpecsmithBridge` keys off this
|
|
81
|
+
single event to flip from ``starting`` → ``waiting`` and to start
|
|
82
|
+
flushing the queued user prompts. Schema is intentionally flat so
|
|
83
|
+
a ``JSON.parse`` line check is enough on the consumer side.
|
|
84
|
+
"""
|
|
85
|
+
payload: dict[str, Any] = {
|
|
86
|
+
"type": "ready",
|
|
87
|
+
"timestamp": _now_iso(),
|
|
88
|
+
"agent": agent,
|
|
89
|
+
"version": version,
|
|
90
|
+
"project_dir": project_dir,
|
|
91
|
+
"provider": provider,
|
|
92
|
+
"model": model,
|
|
93
|
+
"profile_id": profile_id,
|
|
94
|
+
"capabilities": list(capabilities or []),
|
|
95
|
+
}
|
|
96
|
+
payload.update(extra)
|
|
97
|
+
self.emit(payload)
|
|
98
|
+
|
|
99
|
+
def system(self, message: str, **extra: Any) -> None:
|
|
100
|
+
"""Emit a free-form system note (matches bridge.ts handler)."""
|
|
101
|
+
self.emit({"type": "system", "message": message, **extra})
|
|
102
|
+
|
|
103
|
+
def turn_done(self, **extra: Any) -> None:
|
|
104
|
+
"""Emit the per-turn terminator the bridge uses to clear timers."""
|
|
105
|
+
self.emit({"type": "turn_done", "timestamp": _now_iso(), **extra})
|
|
106
|
+
|
|
107
|
+
def error(self, message: str, *, recoverable: bool = False, **extra: Any) -> None:
|
|
108
|
+
"""Emit an error frame (recoverable = retry will be offered)."""
|
|
109
|
+
self.emit(
|
|
110
|
+
{
|
|
111
|
+
"type": "error",
|
|
112
|
+
"timestamp": _now_iso(),
|
|
113
|
+
"message": message,
|
|
114
|
+
"recoverable": bool(recoverable),
|
|
115
|
+
**extra,
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
|
|
61
119
|
# ── Block helpers ────────────────────────────────────────────────────
|
|
62
120
|
|
|
63
121
|
def block_start(self, kind: str, *, agent: str = "nexus", **payload: Any) -> str:
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 BitConcepts, LLC. All rights reserved.
|
|
3
|
+
"""Resilient fallback-chain executor for agent profiles (REQ-146).
|
|
4
|
+
|
|
5
|
+
Profiles in :mod:`specsmith.agent.profiles` carry a ``fallback_chain``
|
|
6
|
+
list of ``"<provider>/<model>"`` or ``"endpoint:<id>"`` strings. When the
|
|
7
|
+
primary call raises a transient error (timeout / connection refused /
|
|
8
|
+
HTTP 429 / HTTP 5xx), this module walks the chain in order until one
|
|
9
|
+
returns successfully or the chain is exhausted.
|
|
10
|
+
|
|
11
|
+
The chain is **resilience**, not **routing** — picking the right primary
|
|
12
|
+
is the routing table's job. The chain only kicks in when the chosen
|
|
13
|
+
primary fails.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import contextlib
|
|
19
|
+
import socket
|
|
20
|
+
from collections.abc import Callable, Iterable
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from typing import Any
|
|
23
|
+
from urllib.error import HTTPError, URLError
|
|
24
|
+
|
|
25
|
+
# (ruff I001 sentinel: imports above are intentionally grouped stdlib + typing)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Errors we treat as worth falling through. Anything else is a programmer
|
|
29
|
+
# bug and should bubble up so we don't paper over correctness issues.
|
|
30
|
+
TRANSIENT_EXCEPTIONS: tuple[type[BaseException], ...] = (
|
|
31
|
+
TimeoutError,
|
|
32
|
+
socket.timeout,
|
|
33
|
+
URLError,
|
|
34
|
+
ConnectionError,
|
|
35
|
+
OSError,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class FallbackAttempt:
|
|
41
|
+
"""One step of an executed chain."""
|
|
42
|
+
|
|
43
|
+
target: str
|
|
44
|
+
ok: bool
|
|
45
|
+
error: str = ""
|
|
46
|
+
|
|
47
|
+
def to_dict(self) -> dict[str, Any]:
|
|
48
|
+
return {"target": self.target, "ok": self.ok, "error": self.error}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class FallbackResult:
|
|
53
|
+
"""Outcome of :func:`run_with_fallback`."""
|
|
54
|
+
|
|
55
|
+
value: Any
|
|
56
|
+
used: str = ""
|
|
57
|
+
attempts: list[FallbackAttempt] = None # type: ignore[assignment]
|
|
58
|
+
|
|
59
|
+
def __post_init__(self) -> None:
|
|
60
|
+
if self.attempts is None:
|
|
61
|
+
self.attempts = []
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _is_transient(exc: BaseException) -> bool:
|
|
65
|
+
if isinstance(exc, HTTPError):
|
|
66
|
+
return 500 <= int(getattr(exc, "code", 0) or 0) < 600 or exc.code in {408, 429}
|
|
67
|
+
return isinstance(exc, TRANSIENT_EXCEPTIONS)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def parse_target(target: str) -> tuple[str, str, str]:
|
|
71
|
+
"""Decompose a chain entry into ``(kind, provider_or_id, model)``.
|
|
72
|
+
|
|
73
|
+
Examples::
|
|
74
|
+
|
|
75
|
+
parse_target("anthropic/claude-haiku-4-5")
|
|
76
|
+
# -> ("provider", "anthropic", "claude-haiku-4-5")
|
|
77
|
+
parse_target("ollama/qwen2.5:7b")
|
|
78
|
+
# -> ("provider", "ollama", "qwen2.5:7b")
|
|
79
|
+
parse_target("endpoint:home-vllm")
|
|
80
|
+
# -> ("endpoint", "home-vllm", "")
|
|
81
|
+
"""
|
|
82
|
+
cleaned = (target or "").strip()
|
|
83
|
+
if not cleaned:
|
|
84
|
+
return ("provider", "", "")
|
|
85
|
+
if cleaned.startswith("endpoint:"):
|
|
86
|
+
return ("endpoint", cleaned[len("endpoint:") :], "")
|
|
87
|
+
if "/" not in cleaned:
|
|
88
|
+
return ("provider", cleaned, "")
|
|
89
|
+
provider, _, model = cleaned.partition("/")
|
|
90
|
+
return ("provider", provider.strip(), model.strip())
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def run_with_fallback(
|
|
94
|
+
primary_target: str,
|
|
95
|
+
fallback_chain: Iterable[str],
|
|
96
|
+
invoke: Callable[[str, str, str], Any],
|
|
97
|
+
*,
|
|
98
|
+
on_attempt: Callable[[FallbackAttempt], None] | None = None,
|
|
99
|
+
) -> FallbackResult:
|
|
100
|
+
"""Try the primary target; on transient failure walk the chain.
|
|
101
|
+
|
|
102
|
+
``invoke`` is called as ``invoke(kind, provider_or_id, model)`` and
|
|
103
|
+
must raise on failure. Any non-transient exception aborts the chain
|
|
104
|
+
immediately (we don't want to mask a programmer bug as an outage).
|
|
105
|
+
"""
|
|
106
|
+
targets = [primary_target] + [t for t in fallback_chain if t]
|
|
107
|
+
result = FallbackResult(value=None, attempts=[])
|
|
108
|
+
for target in targets:
|
|
109
|
+
kind, ident, model = parse_target(target)
|
|
110
|
+
if not ident:
|
|
111
|
+
continue
|
|
112
|
+
try:
|
|
113
|
+
value = invoke(kind, ident, model)
|
|
114
|
+
except Exception as exc: # noqa: BLE001
|
|
115
|
+
attempt = FallbackAttempt(target=target, ok=False, error=str(exc))
|
|
116
|
+
result.attempts.append(attempt)
|
|
117
|
+
if on_attempt:
|
|
118
|
+
with contextlib.suppress(Exception):
|
|
119
|
+
on_attempt(attempt)
|
|
120
|
+
if not _is_transient(exc):
|
|
121
|
+
# Programmer error / auth failure — bubble up immediately
|
|
122
|
+
# so the caller sees the real cause.
|
|
123
|
+
raise
|
|
124
|
+
continue
|
|
125
|
+
attempt = FallbackAttempt(target=target, ok=True)
|
|
126
|
+
result.attempts.append(attempt)
|
|
127
|
+
if on_attempt:
|
|
128
|
+
with contextlib.suppress(Exception):
|
|
129
|
+
on_attempt(attempt)
|
|
130
|
+
result.value = value
|
|
131
|
+
result.used = target
|
|
132
|
+
return result
|
|
133
|
+
return result # exhausted, value=None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
__all__ = [
|
|
137
|
+
"FallbackAttempt",
|
|
138
|
+
"FallbackResult",
|
|
139
|
+
"TRANSIENT_EXCEPTIONS",
|
|
140
|
+
"parse_target",
|
|
141
|
+
"run_with_fallback",
|
|
142
|
+
]
|