specsmith 0.6.0.dev231__tar.gz → 0.6.0.dev233__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.6.0.dev231/src/specsmith.egg-info → specsmith-0.6.0.dev233}/PKG-INFO +6 -1
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/pyproject.toml +12 -1
- specsmith-0.6.0.dev233/src/specsmith/agent/suggester.py +264 -0
- specsmith-0.6.0.dev233/src/specsmith/block_export.py +106 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/cli.py +153 -0
- specsmith-0.6.0.dev233/src/specsmith/cloud_serve.py +150 -0
- specsmith-0.6.0.dev233/src/specsmith/drive.py +126 -0
- specsmith-0.6.0.dev233/src/specsmith/history_search.py +159 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233/src/specsmith.egg-info}/PKG-INFO +6 -1
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith.egg-info/SOURCES.txt +8 -1
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith.egg-info/requires.txt +7 -0
- specsmith-0.6.0.dev233/tests/test_suggester.py +88 -0
- specsmith-0.6.0.dev233/tests/test_warp_parity.py +421 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/LICENSE +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/README.md +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/setup.cfg +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/epistemic/__init__.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/epistemic/belief.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/epistemic/certainty.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/epistemic/failure_graph.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/epistemic/py.typed +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/epistemic/recovery.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/epistemic/session.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/epistemic/stress_tester.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/epistemic/trace.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/__init__.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/__main__.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/agent/__init__.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/agent/broker.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/agent/chat_runner.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/agent/cleanup.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/agent/events.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/agent/indexer.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/agent/mcp.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/agent/memory.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/agent/orchestrator.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/agent/repl.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/agent/router.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/agent/rules.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/agent/safety.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/agent/tools.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/agent/verifier.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/architect.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/auditor.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/auth.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/commands/__init__.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/compressor.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/config.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/console_utils.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/credit_analyzer.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/credits.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/differ.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/doctor.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/epistemic/__init__.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/epistemic/belief.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/epistemic/certainty.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/epistemic/failure_graph.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/epistemic/recovery.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/epistemic/stress_tester.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/executor.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/exporter.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/gui/__init__.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/gui/app.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/gui/main_window.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/gui/session_tab.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/gui/theme.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/gui/widgets/__init__.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/gui/widgets/chat_view.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/gui/widgets/input_bar.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/gui/widgets/provider_bar.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/gui/widgets/token_meter.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/gui/widgets/tool_panel.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/gui/widgets/update_checker.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/gui/worker.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/importer.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/integrations/__init__.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/integrations/agent_skill.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/integrations/aider.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/integrations/base.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/integrations/claude_code.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/integrations/copilot.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/integrations/cursor.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/integrations/gemini.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/integrations/windsurf.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/languages.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/ledger.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/patent.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/phase.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/plugins.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/profiles.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/rate_limits.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/releaser.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/requirements.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/requirements_parser.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/retrieval.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/scaffolder.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/serve.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/session.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/skills.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/agents.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/community/bug_report.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/community/code_of_conduct.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/community/contributing.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/community/feature_request.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/community/license-Apache-2.0.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/community/license-MIT.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/community/pull_request_template.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/community/security.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/docs/architecture.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/docs/mkdocs.yml.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/docs/readthedocs.yaml.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/docs/requirements.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/docs/test-spec.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/editorconfig.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/gitattributes.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/gitignore.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/go/go.mod.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/go/main.go.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/belief-registry.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/context-budget.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/drift-metrics.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/epistemic-axioms.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/failure-modes.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/lifecycle.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/roles.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/rules.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/session-protocol.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/uncertainty-map.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/verification.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/js/package.json.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/ledger.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/python/cli.py.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/python/init.py.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/python/pyproject.toml.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/readme.md.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/rust/Cargo.toml.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/rust/main.rs.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/scripts/exec.cmd.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/scripts/exec.sh.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/scripts/run.cmd.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/scripts/run.sh.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/scripts/setup.cmd.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/scripts/setup.sh.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/templates/workflows/release.yml.j2 +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/tool_installer.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/toolrules.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/tools.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/trace.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/updater.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/upgrader.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/validator.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/vcs/__init__.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/vcs/base.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/vcs/bitbucket.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/vcs/github.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/vcs/gitlab.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/vcs_commands.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/wireframes.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith/workspace.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith.egg-info/dependency_links.txt +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith.egg-info/entry_points.txt +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/src/specsmith.egg-info/top_level.txt +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_CMD_001.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_auditor.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_chat_diff_decision.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_chat_stdin_protocol.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_cli.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_cli_workflows_history_drive.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_compressor.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_e2e_nexus.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_epistemic.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_importer.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_integrations.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_mcp_client.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_nexus.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_phase1_4_new.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_phase34_completion.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_rate_limits.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_scaffolder.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_skill_marketplace.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_smoke.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_tools.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_validator.py +0 -0
- {specsmith-0.6.0.dev231 → specsmith-0.6.0.dev233}/tests/test_vcs.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: specsmith
|
|
3
|
-
Version: 0.6.0.
|
|
3
|
+
Version: 0.6.0.dev233
|
|
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
|
|
@@ -53,6 +53,11 @@ Provides-Extra: gui
|
|
|
53
53
|
Requires-Dist: PySide6>=6.6; extra == "gui"
|
|
54
54
|
Provides-Extra: ag2
|
|
55
55
|
Requires-Dist: ag2[ollama]; extra == "ag2"
|
|
56
|
+
Provides-Extra: history-semantic
|
|
57
|
+
Requires-Dist: sentence-transformers>=2.2; extra == "history-semantic"
|
|
58
|
+
Requires-Dist: numpy>=1.24; extra == "history-semantic"
|
|
59
|
+
Provides-Extra: voice
|
|
60
|
+
Requires-Dist: whisper-cpp-python>=0.2; extra == "voice"
|
|
56
61
|
Provides-Extra: agent
|
|
57
62
|
Requires-Dist: anthropic>=0.56; extra == "agent"
|
|
58
63
|
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.6.0.
|
|
7
|
+
version = "0.6.0.dev233"
|
|
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"
|
|
@@ -67,6 +67,11 @@ mistral = ["openai>=1.0"] # Mistral uses the openai SDK pointed at api.mistral.
|
|
|
67
67
|
gui = ["PySide6>=6.6"]
|
|
68
68
|
# AG2 agent shell (Planner/Builder/Verifier over Ollama)
|
|
69
69
|
ag2 = ["ag2[ollama]"]
|
|
70
|
+
# Optional semantic backend for `specsmith history search --semantic` (REQ-135).
|
|
71
|
+
# Falls back gracefully to keyword matching if these are not installed.
|
|
72
|
+
history-semantic = ["sentence-transformers>=2.2", "numpy>=1.24"]
|
|
73
|
+
# Optional whisper-cpp wrapper for the voice agent input (REQ-141).
|
|
74
|
+
voice = ["whisper-cpp-python>=0.2"]
|
|
70
75
|
# Install all optional LLM providers
|
|
71
76
|
agent = ["anthropic>=0.56", "openai>=1.0"]
|
|
72
77
|
# Convenience bundle: everything
|
|
@@ -138,6 +143,12 @@ module = [
|
|
|
138
143
|
"yaml.*",
|
|
139
144
|
"keyring", # optional OS credential store; stubs not published
|
|
140
145
|
"keyring.*",
|
|
146
|
+
"numpy", # optional [history-semantic] extra (REQ-135)
|
|
147
|
+
"numpy.*",
|
|
148
|
+
"sentence_transformers", # optional [history-semantic] extra (REQ-135)
|
|
149
|
+
"sentence_transformers.*",
|
|
150
|
+
"whisper_cpp_python", # optional [voice] extra (REQ-141)
|
|
151
|
+
"whisper_cpp_python.*",
|
|
141
152
|
]
|
|
142
153
|
ignore_missing_imports = true
|
|
143
154
|
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 BitConcepts, LLC. All rights reserved.
|
|
3
|
+
"""Lightweight NL-to-command suggester for `specsmith suggest-command` (REQ-131).
|
|
4
|
+
|
|
5
|
+
Given a partial natural-language fragment, return a structured suggestion
|
|
6
|
+
that the VS Code extension renders as inline ghost-text in the chat input.
|
|
7
|
+
Three classification buckets:
|
|
8
|
+
|
|
9
|
+
* ``command`` -- the input is shell-y (starts with an imperative verb that
|
|
10
|
+
maps to a known CLI). Suggest a concrete shell command.
|
|
11
|
+
* ``utterance`` -- the input is plain English meant for the agent. Suggest
|
|
12
|
+
a refined utterance that names a likely component (best-effort).
|
|
13
|
+
* ``passthrough`` -- input is too short or ambiguous; echo it back so the
|
|
14
|
+
ghost-text matches what the user typed (no-op suggestion).
|
|
15
|
+
|
|
16
|
+
The suggester is **deterministic and LLM-free**. The IDE may layer an LLM
|
|
17
|
+
predictor on top, but the CLI baseline must always succeed quickly. If the
|
|
18
|
+
extension wants a richer suggestion, it can call `specsmith preflight
|
|
19
|
+
--predict-only` separately for utterances.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import re
|
|
25
|
+
from dataclasses import dataclass, field
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
# Common imperative verbs that map to shell-y intents.
|
|
30
|
+
_SHELL_VERBS = {
|
|
31
|
+
"run",
|
|
32
|
+
"exec",
|
|
33
|
+
"execute",
|
|
34
|
+
"kill",
|
|
35
|
+
"stop",
|
|
36
|
+
"start",
|
|
37
|
+
"restart",
|
|
38
|
+
"build",
|
|
39
|
+
"test",
|
|
40
|
+
"lint",
|
|
41
|
+
"format",
|
|
42
|
+
"git",
|
|
43
|
+
"cd",
|
|
44
|
+
"ls",
|
|
45
|
+
"cat",
|
|
46
|
+
"rm",
|
|
47
|
+
"mv",
|
|
48
|
+
"cp",
|
|
49
|
+
"find",
|
|
50
|
+
"grep",
|
|
51
|
+
"ps",
|
|
52
|
+
"top",
|
|
53
|
+
"open",
|
|
54
|
+
"edit",
|
|
55
|
+
"tail",
|
|
56
|
+
"head",
|
|
57
|
+
"make",
|
|
58
|
+
"npm",
|
|
59
|
+
"pnpm",
|
|
60
|
+
"yarn",
|
|
61
|
+
"pip",
|
|
62
|
+
"pipx",
|
|
63
|
+
"uv",
|
|
64
|
+
"pytest",
|
|
65
|
+
"ruff",
|
|
66
|
+
"mypy",
|
|
67
|
+
"cargo",
|
|
68
|
+
"go",
|
|
69
|
+
"docker",
|
|
70
|
+
"kubectl",
|
|
71
|
+
"terraform",
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# Map verb -> default refined command.
|
|
75
|
+
_VERB_TEMPLATES: dict[str, str] = {
|
|
76
|
+
"run tests": "pytest -q",
|
|
77
|
+
"run lint": "ruff check .",
|
|
78
|
+
"run mypy": "mypy src/",
|
|
79
|
+
"format": "ruff format .",
|
|
80
|
+
"lint": "ruff check .",
|
|
81
|
+
"test": "pytest -q",
|
|
82
|
+
"build": "python -m build",
|
|
83
|
+
"git status": "git --no-pager status",
|
|
84
|
+
"git log": "git --no-pager log --oneline -20",
|
|
85
|
+
"git diff": "git --no-pager diff",
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class CommandSuggestion:
|
|
91
|
+
"""Output payload of :func:`suggest_command`."""
|
|
92
|
+
|
|
93
|
+
kind: str # "command" | "utterance" | "passthrough"
|
|
94
|
+
suggestion: str
|
|
95
|
+
confidence: float = 0.5
|
|
96
|
+
reasoning: str = ""
|
|
97
|
+
candidates: list[str] = field(default_factory=list)
|
|
98
|
+
|
|
99
|
+
def to_dict(self) -> dict[str, Any]:
|
|
100
|
+
return {
|
|
101
|
+
"kind": self.kind,
|
|
102
|
+
"suggestion": self.suggestion,
|
|
103
|
+
"confidence": round(self.confidence, 3),
|
|
104
|
+
"reasoning": self.reasoning,
|
|
105
|
+
"candidates": list(self.candidates),
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def classify(text: str) -> str:
|
|
110
|
+
"""Return ``command``, ``utterance``, or ``passthrough``."""
|
|
111
|
+
stripped = text.strip()
|
|
112
|
+
if len(stripped) < 2:
|
|
113
|
+
return "passthrough"
|
|
114
|
+
first = stripped.split()[0].lower()
|
|
115
|
+
if first in _SHELL_VERBS:
|
|
116
|
+
return "command"
|
|
117
|
+
return "utterance"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def suggest_command(text: str, *, project_dir: Path | None = None) -> CommandSuggestion:
|
|
121
|
+
"""Return a structured suggestion for ``text``.
|
|
122
|
+
|
|
123
|
+
The suggester is deterministic. It looks for verb prefixes and a short
|
|
124
|
+
catalogue of common templates; if nothing matches, it returns the input
|
|
125
|
+
unchanged with kind=``passthrough``.
|
|
126
|
+
"""
|
|
127
|
+
stripped = text.strip()
|
|
128
|
+
kind = classify(stripped)
|
|
129
|
+
if kind == "passthrough":
|
|
130
|
+
return CommandSuggestion(
|
|
131
|
+
kind="passthrough",
|
|
132
|
+
suggestion=text,
|
|
133
|
+
confidence=0.0,
|
|
134
|
+
reasoning="input too short to suggest",
|
|
135
|
+
)
|
|
136
|
+
if kind == "utterance":
|
|
137
|
+
return _suggest_utterance(stripped, project_dir=project_dir)
|
|
138
|
+
return _suggest_shell(stripped)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _suggest_shell(text: str) -> CommandSuggestion:
|
|
142
|
+
lower = text.lower()
|
|
143
|
+
# Direct multi-word template match (e.g. "run tests").
|
|
144
|
+
for phrase, command in _VERB_TEMPLATES.items():
|
|
145
|
+
if lower.startswith(phrase):
|
|
146
|
+
return CommandSuggestion(
|
|
147
|
+
kind="command",
|
|
148
|
+
suggestion=command,
|
|
149
|
+
confidence=0.85,
|
|
150
|
+
reasoning=f"matched template '{phrase}'",
|
|
151
|
+
)
|
|
152
|
+
# Single-verb fallback: if the user typed "git" alone, propose
|
|
153
|
+
# `git status`. If "test", propose pytest -q.
|
|
154
|
+
first = lower.split()[0]
|
|
155
|
+
fallback = {
|
|
156
|
+
"git": "git --no-pager status",
|
|
157
|
+
"ls": "ls -la",
|
|
158
|
+
"test": "pytest -q",
|
|
159
|
+
"lint": "ruff check .",
|
|
160
|
+
"format": "ruff format .",
|
|
161
|
+
"build": "python -m build",
|
|
162
|
+
"find": "find . -name '*.py'",
|
|
163
|
+
}.get(first)
|
|
164
|
+
if fallback and lower.strip() == first:
|
|
165
|
+
return CommandSuggestion(
|
|
166
|
+
kind="command",
|
|
167
|
+
suggestion=fallback,
|
|
168
|
+
confidence=0.7,
|
|
169
|
+
reasoning=f"single verb '{first}' resolved to default command",
|
|
170
|
+
)
|
|
171
|
+
# Pass through what the user typed; mark as command anyway so the IDE
|
|
172
|
+
# knows it's shell-y rather than NL.
|
|
173
|
+
return CommandSuggestion(
|
|
174
|
+
kind="command",
|
|
175
|
+
suggestion=text,
|
|
176
|
+
confidence=0.3,
|
|
177
|
+
reasoning="recognised as shell command but no template applied",
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
_REQ_REGEX = re.compile(r"REQ-[A-Z0-9-]+", re.IGNORECASE)
|
|
182
|
+
_KNOWN_VERBS = ("add", "fix", "refactor", "remove", "rename", "document", "test")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _suggest_utterance(text: str, *, project_dir: Path | None) -> CommandSuggestion:
|
|
186
|
+
lower = text.lower()
|
|
187
|
+
candidates: list[str] = []
|
|
188
|
+
|
|
189
|
+
# If the text already names a REQ, surface it verbatim with a higher
|
|
190
|
+
# confidence — the user is already specific.
|
|
191
|
+
matched = _REQ_REGEX.findall(text)
|
|
192
|
+
if matched:
|
|
193
|
+
return CommandSuggestion(
|
|
194
|
+
kind="utterance",
|
|
195
|
+
suggestion=text,
|
|
196
|
+
confidence=0.9,
|
|
197
|
+
reasoning=f"references {matched[0]} explicitly",
|
|
198
|
+
candidates=matched,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# If the text starts with a change verb but doesn't name a component,
|
|
202
|
+
# suggest a refined version that asks the user to add a target.
|
|
203
|
+
first = lower.split()[0] if lower.split() else ""
|
|
204
|
+
if first in _KNOWN_VERBS and len(lower.split()) <= 3:
|
|
205
|
+
return CommandSuggestion(
|
|
206
|
+
kind="utterance",
|
|
207
|
+
suggestion=f"{text.rstrip()} (please name the component or file)",
|
|
208
|
+
confidence=0.6,
|
|
209
|
+
reasoning=f"verb '{first}' lacks an explicit target",
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Project-aware refinement: scan REQUIREMENTS.md for keywords that match
|
|
213
|
+
# the input and propose the first hit. Best-effort; never blocks.
|
|
214
|
+
if project_dir is not None:
|
|
215
|
+
candidates = _scan_requirements(text, project_dir)
|
|
216
|
+
if candidates:
|
|
217
|
+
return CommandSuggestion(
|
|
218
|
+
kind="utterance",
|
|
219
|
+
suggestion=f"{text.rstrip()} ({candidates[0]})",
|
|
220
|
+
confidence=0.65,
|
|
221
|
+
reasoning=f"matched {candidates[0]} from REQUIREMENTS.md",
|
|
222
|
+
candidates=candidates,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# Default: echo back unchanged.
|
|
226
|
+
return CommandSuggestion(
|
|
227
|
+
kind="utterance",
|
|
228
|
+
suggestion=text,
|
|
229
|
+
confidence=0.4,
|
|
230
|
+
reasoning="no project-specific refinement available",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _scan_requirements(text: str, project_dir: Path) -> list[str]:
|
|
235
|
+
"""Return up to 5 REQ ids whose description shares words with ``text``."""
|
|
236
|
+
candidates: list[tuple[int, str]] = []
|
|
237
|
+
for path in (
|
|
238
|
+
project_dir / "REQUIREMENTS.md",
|
|
239
|
+
project_dir / "docs" / "REQUIREMENTS.md",
|
|
240
|
+
):
|
|
241
|
+
if not path.is_file():
|
|
242
|
+
continue
|
|
243
|
+
try:
|
|
244
|
+
content = path.read_text(encoding="utf-8")
|
|
245
|
+
except OSError:
|
|
246
|
+
continue
|
|
247
|
+
words = {w.lower() for w in re.findall(r"[A-Za-z]{4,}", text)}
|
|
248
|
+
if not words:
|
|
249
|
+
return []
|
|
250
|
+
for match in re.finditer(
|
|
251
|
+
r"^###?\s+(REQ-[A-Z0-9-]+)\s*(.*?)(?=^###?\s+REQ|^##\s|\Z)",
|
|
252
|
+
content,
|
|
253
|
+
re.MULTILINE | re.DOTALL,
|
|
254
|
+
):
|
|
255
|
+
req_id, body = match.group(1), match.group(2)
|
|
256
|
+
body_words = {w.lower() for w in re.findall(r"[A-Za-z]{4,}", body)}
|
|
257
|
+
score = len(words & body_words)
|
|
258
|
+
if score > 0:
|
|
259
|
+
candidates.append((score, req_id))
|
|
260
|
+
candidates.sort(key=lambda x: (-x[0], x[1]))
|
|
261
|
+
return [req for _, req in candidates[:5]]
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
__all__ = ["CommandSuggestion", "classify", "suggest_command"]
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 BitConcepts, LLC. All rights reserved.
|
|
3
|
+
"""Per-block share/export for `specsmith chat` (REQ-134).
|
|
4
|
+
|
|
5
|
+
Reads ``.specsmith/sessions/<session_id>/events.jsonl`` (the chat replay log
|
|
6
|
+
or, fallback, ``turns.jsonl``) and slices a single block out as a
|
|
7
|
+
self-contained Markdown / JSON / HTML snippet.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import html
|
|
13
|
+
import json
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _events_path(project_dir: Path, session_id: str) -> Path | None:
|
|
19
|
+
base = project_dir / ".specsmith" / "sessions" / session_id
|
|
20
|
+
candidates = [
|
|
21
|
+
base / "events.jsonl",
|
|
22
|
+
base / "turns.jsonl",
|
|
23
|
+
]
|
|
24
|
+
for c in candidates:
|
|
25
|
+
if c.is_file():
|
|
26
|
+
return c
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _read_events(events_path: Path) -> list[dict[str, Any]]:
|
|
31
|
+
out: list[dict[str, Any]] = []
|
|
32
|
+
for line in events_path.read_text(encoding="utf-8").splitlines():
|
|
33
|
+
line = line.strip()
|
|
34
|
+
if not line:
|
|
35
|
+
continue
|
|
36
|
+
try:
|
|
37
|
+
obj = json.loads(line)
|
|
38
|
+
except ValueError:
|
|
39
|
+
continue
|
|
40
|
+
if isinstance(obj, dict):
|
|
41
|
+
out.append(obj)
|
|
42
|
+
return out
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def slice_block(events: list[dict[str, Any]], block_id: str) -> list[dict[str, Any]]:
|
|
46
|
+
"""Return all events tagged with ``block_id``, plus the bracketing
|
|
47
|
+
block_start/block_complete events that defined it.
|
|
48
|
+
"""
|
|
49
|
+
out: list[dict[str, Any]] = []
|
|
50
|
+
for evt in events:
|
|
51
|
+
if evt.get("block_id") == block_id or evt.get("id") == block_id:
|
|
52
|
+
out.append(evt)
|
|
53
|
+
return out
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def export_block(
|
|
57
|
+
project_dir: Path,
|
|
58
|
+
session_id: str,
|
|
59
|
+
block_id: str,
|
|
60
|
+
*,
|
|
61
|
+
fmt: str = "md",
|
|
62
|
+
) -> str:
|
|
63
|
+
"""Export the events for ``block_id`` as a string in ``fmt``.
|
|
64
|
+
|
|
65
|
+
Raises FileNotFoundError if no session log exists.
|
|
66
|
+
Raises KeyError if the block is not found.
|
|
67
|
+
"""
|
|
68
|
+
events_path = _events_path(project_dir, session_id)
|
|
69
|
+
if events_path is None:
|
|
70
|
+
raise FileNotFoundError(f"No session log for {session_id} in {project_dir}")
|
|
71
|
+
events = _read_events(events_path)
|
|
72
|
+
matching = slice_block(events, block_id)
|
|
73
|
+
if not matching:
|
|
74
|
+
raise KeyError(f"block_id {block_id} not found in session {session_id}")
|
|
75
|
+
if fmt == "json":
|
|
76
|
+
return json.dumps(matching, indent=2)
|
|
77
|
+
if fmt == "html":
|
|
78
|
+
rows = "".join(
|
|
79
|
+
f"<li><pre>{html.escape(json.dumps(evt, indent=2))}</pre></li>" for evt in matching
|
|
80
|
+
)
|
|
81
|
+
return (
|
|
82
|
+
f"<!DOCTYPE html><html><head><meta charset='utf-8'>"
|
|
83
|
+
f"<title>specsmith block {html.escape(block_id)}</title></head>"
|
|
84
|
+
f"<body><h1>Block {html.escape(block_id)}</h1>"
|
|
85
|
+
f"<p>session {html.escape(session_id)}</p><ol>{rows}</ol></body></html>"
|
|
86
|
+
)
|
|
87
|
+
# default: markdown
|
|
88
|
+
lines: list[str] = [
|
|
89
|
+
f"# Block `{block_id}`",
|
|
90
|
+
f"_session_: `{session_id}`",
|
|
91
|
+
"",
|
|
92
|
+
]
|
|
93
|
+
for evt in matching:
|
|
94
|
+
kind = str(evt.get("type", "event"))
|
|
95
|
+
lines.append(f"## {kind}")
|
|
96
|
+
if "text" in evt:
|
|
97
|
+
lines.append(str(evt["text"]))
|
|
98
|
+
else:
|
|
99
|
+
lines.append("```json")
|
|
100
|
+
lines.append(json.dumps(evt, indent=2))
|
|
101
|
+
lines.append("```")
|
|
102
|
+
lines.append("")
|
|
103
|
+
return "\n".join(lines)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
__all__ = ["export_block", "slice_block"]
|
|
@@ -4440,6 +4440,159 @@ def info_cmd(as_json: bool, section: str) -> None:
|
|
|
4440
4440
|
# ---------------------------------------------------------------------------
|
|
4441
4441
|
|
|
4442
4442
|
|
|
4443
|
+
# ---------------------------------------------------------------------------
|
|
4444
|
+
# specsmith chat-export-block — self-contained block share (REQ-134)
|
|
4445
|
+
# ---------------------------------------------------------------------------
|
|
4446
|
+
#
|
|
4447
|
+
# This is exposed at the top level (rather than under ``chat``) because the
|
|
4448
|
+
# existing ``specsmith chat <utterance>`` command takes a positional argument
|
|
4449
|
+
# and cannot simultaneously act as a Click group.
|
|
4450
|
+
|
|
4451
|
+
|
|
4452
|
+
@main.command(name="chat-export-block")
|
|
4453
|
+
@click.option("--project-dir", type=click.Path(exists=True), default=".")
|
|
4454
|
+
@click.option("--session-id", "session_id", required=True)
|
|
4455
|
+
@click.option("--block-id", "block_id", required=True)
|
|
4456
|
+
@click.option(
|
|
4457
|
+
"--format",
|
|
4458
|
+
"fmt",
|
|
4459
|
+
type=click.Choice(["md", "json", "html"]),
|
|
4460
|
+
default="md",
|
|
4461
|
+
)
|
|
4462
|
+
def chat_export_block_cmd(project_dir: str, session_id: str, block_id: str, fmt: str) -> None:
|
|
4463
|
+
"""Export one chat block as a self-contained snippet (REQ-134)."""
|
|
4464
|
+
from specsmith.block_export import export_block
|
|
4465
|
+
|
|
4466
|
+
try:
|
|
4467
|
+
out = export_block(
|
|
4468
|
+
Path(project_dir).resolve(),
|
|
4469
|
+
session_id,
|
|
4470
|
+
block_id,
|
|
4471
|
+
fmt=fmt,
|
|
4472
|
+
)
|
|
4473
|
+
except FileNotFoundError as exc:
|
|
4474
|
+
console.print(f"[red]{exc}[/red]")
|
|
4475
|
+
raise SystemExit(1) from exc
|
|
4476
|
+
except KeyError as exc:
|
|
4477
|
+
console.print(f"[red]{exc}[/red]")
|
|
4478
|
+
raise SystemExit(1) from exc
|
|
4479
|
+
click.echo(out)
|
|
4480
|
+
|
|
4481
|
+
|
|
4482
|
+
# ---------------------------------------------------------------------------
|
|
4483
|
+
# specsmith cloud serve — reference cloud-agent receiver (REQ-136)
|
|
4484
|
+
# ---------------------------------------------------------------------------
|
|
4485
|
+
|
|
4486
|
+
|
|
4487
|
+
@main.command(name="cloud-serve")
|
|
4488
|
+
@click.option("--host", default="127.0.0.1")
|
|
4489
|
+
@click.option("--port", type=int, default=9000)
|
|
4490
|
+
@click.option("--token", default="", help="Optional bearer token.")
|
|
4491
|
+
@click.option("--allow-cidr", default="", help="CIDR range required to bind non-loopback.")
|
|
4492
|
+
def cloud_serve_cmd(host: str, port: int, token: str, allow_cidr: str) -> None:
|
|
4493
|
+
"""Run the reference cloud-agent receiver (REQ-136).
|
|
4494
|
+
|
|
4495
|
+
Accepts POST /spawn with a JSON manifest, persists it under
|
|
4496
|
+
~/.specsmith/cloud-runs/<run_id>/manifest.json, and returns 202 with
|
|
4497
|
+
a stream_url placeholder.
|
|
4498
|
+
"""
|
|
4499
|
+
from specsmith.cloud_serve import CloudReceiverConfig, make_server
|
|
4500
|
+
|
|
4501
|
+
config = CloudReceiverConfig(host=host, port=port, token=token, allow_cidr=allow_cidr)
|
|
4502
|
+
try:
|
|
4503
|
+
server = make_server(config)
|
|
4504
|
+
except RuntimeError as exc:
|
|
4505
|
+
console.print(f"[red]{exc}[/red]")
|
|
4506
|
+
raise SystemExit(2) from exc
|
|
4507
|
+
console.print(
|
|
4508
|
+
f"[bold]specsmith cloud serve[/bold] on http://{config.host}:{config.port}\n"
|
|
4509
|
+
f" storage: {config.storage_dir}\n"
|
|
4510
|
+
f" token: {'(set)' if token else '(none)'}\n"
|
|
4511
|
+
" Press Ctrl+C to stop."
|
|
4512
|
+
)
|
|
4513
|
+
try:
|
|
4514
|
+
server.serve_forever()
|
|
4515
|
+
except KeyboardInterrupt:
|
|
4516
|
+
console.print("\n[dim]cloud serve stopped.[/dim]")
|
|
4517
|
+
server.server_close()
|
|
4518
|
+
|
|
4519
|
+
|
|
4520
|
+
# ---------------------------------------------------------------------------
|
|
4521
|
+
# specsmith api-surface — 1.0 stability snapshot (REQ-140)
|
|
4522
|
+
# ---------------------------------------------------------------------------
|
|
4523
|
+
|
|
4524
|
+
|
|
4525
|
+
@main.command(name="api-surface")
|
|
4526
|
+
@click.option(
|
|
4527
|
+
"--snapshot",
|
|
4528
|
+
type=click.Path(),
|
|
4529
|
+
default="",
|
|
4530
|
+
help="Write the current public surface to this JSON file.",
|
|
4531
|
+
)
|
|
4532
|
+
def api_surface_cmd(snapshot: str) -> None:
|
|
4533
|
+
"""Print the frozen public CLI/API surface as JSON (REQ-140)."""
|
|
4534
|
+
import json as _json
|
|
4535
|
+
|
|
4536
|
+
surface = {
|
|
4537
|
+
"cli_commands": sorted(
|
|
4538
|
+
cmd_name for cmd_name in main.commands if not cmd_name.startswith("_")
|
|
4539
|
+
),
|
|
4540
|
+
"exit_codes": {
|
|
4541
|
+
"preflight_accepted": 0,
|
|
4542
|
+
"preflight_needs_clarification": 2,
|
|
4543
|
+
"preflight_blocked": 3,
|
|
4544
|
+
"verify_ok": 0,
|
|
4545
|
+
"verify_retry": 2,
|
|
4546
|
+
"verify_stop": 3,
|
|
4547
|
+
},
|
|
4548
|
+
"event_types": [
|
|
4549
|
+
"block_start",
|
|
4550
|
+
"block_complete",
|
|
4551
|
+
"token",
|
|
4552
|
+
"plan_step",
|
|
4553
|
+
"tool_call",
|
|
4554
|
+
"tool_request",
|
|
4555
|
+
"tool_result",
|
|
4556
|
+
"diff",
|
|
4557
|
+
"task_complete",
|
|
4558
|
+
],
|
|
4559
|
+
}
|
|
4560
|
+
payload = _json.dumps(surface, indent=2, sort_keys=True)
|
|
4561
|
+
if snapshot:
|
|
4562
|
+
Path(snapshot).write_text(payload, encoding="utf-8")
|
|
4563
|
+
click.echo(payload)
|
|
4564
|
+
|
|
4565
|
+
|
|
4566
|
+
# ---------------------------------------------------------------------------
|
|
4567
|
+
# specsmith suggest-command — NL-to-command suggester (REQ-131)
|
|
4568
|
+
# ---------------------------------------------------------------------------
|
|
4569
|
+
|
|
4570
|
+
|
|
4571
|
+
@main.command(name="suggest-command")
|
|
4572
|
+
@click.argument("text")
|
|
4573
|
+
@click.option("--project-dir", type=click.Path(exists=True), default=".")
|
|
4574
|
+
@click.option(
|
|
4575
|
+
"--json",
|
|
4576
|
+
"as_json",
|
|
4577
|
+
is_flag=True,
|
|
4578
|
+
default=True,
|
|
4579
|
+
help="Emit suggestion as JSON (default; only mode for now).",
|
|
4580
|
+
)
|
|
4581
|
+
def suggest_command_cmd(text: str, project_dir: str, as_json: bool) -> None:
|
|
4582
|
+
"""Suggest a refined command or utterance for a partial input (REQ-131).
|
|
4583
|
+
|
|
4584
|
+
Returns a JSON object: ``{kind, suggestion, confidence, reasoning, candidates}``.
|
|
4585
|
+
``kind`` is one of ``command``, ``utterance``, ``passthrough``. The
|
|
4586
|
+
extension renders the suggestion as inline ghost-text.
|
|
4587
|
+
"""
|
|
4588
|
+
import json as _json
|
|
4589
|
+
|
|
4590
|
+
from specsmith.agent.suggester import suggest_command
|
|
4591
|
+
|
|
4592
|
+
result = suggest_command(text, project_dir=Path(project_dir).resolve())
|
|
4593
|
+
click.echo(_json.dumps(result.to_dict(), indent=2))
|
|
4594
|
+
|
|
4595
|
+
|
|
4443
4596
|
@main.command(name="scan")
|
|
4444
4597
|
@click.option("--project-dir", type=click.Path(exists=True), default=".")
|
|
4445
4598
|
@click.option("--json", "as_json", is_flag=True, default=False, help="Output as JSON.")
|