specsmith 0.15.3.dev570__tar.gz → 0.15.3.dev573__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.15.3.dev570/src/specsmith.egg-info → specsmith-0.15.3.dev573}/PKG-INFO +2 -2
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/pyproject.toml +2 -2
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/auditor.py +11 -4
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/cli.py +217 -44
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/esdb/sqlite_store.py +16 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/sync.py +143 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/gitignore.j2 +7 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573/src/specsmith.egg-info}/PKG-INFO +2 -2
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith.egg-info/SOURCES.txt +2 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith.egg-info/requires.txt +1 -1
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_auditor.py +18 -1
- specsmith-0.15.3.dev573/tests/test_cli_regressions.py +88 -0
- specsmith-0.15.3.dev573/tests/test_esdb_enforcement.py +130 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_scaffolder.py +3 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/LICENSE +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/README.md +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/setup.cfg +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/epistemic/__init__.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/epistemic/belief.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/epistemic/certainty.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/epistemic/failure_graph.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/epistemic/py.typed +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/epistemic/recovery.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/epistemic/session.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/epistemic/stress_tester.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/epistemic/trace.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/__init__.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/__main__.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/__init__.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/broker.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/chat_runner.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/cleanup.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/context_seed.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/core.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/dispatch/__init__.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/dispatch/_status.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/dispatch/dag.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/dispatch/dispatcher.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/dispatch/events.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/dispatch/result.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/endpoints.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/events.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/execution_profiles.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/fallback.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/hf_leaderboard.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/hf_sync.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/indexer.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/llm_client.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/mcp.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/memory.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/model_intelligence.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/model_profiles.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/orchestrator.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/permissions.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/profiles.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/provider_registry.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/repl.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/router.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/rules.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/runner.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/safety.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/spawner.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/suggester.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/teams.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/tools.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/verifier.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/voice.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/architect.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/auth.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/block_export.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/channel.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/ci_manager.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/commands/__init__.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/commands/intelligence.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/compliance/__init__.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/compliance/_compat.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/compliance/checker.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/compliance/evidence.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/compliance/regulations.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/compliance/reporter.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/compressor.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/config.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/console_utils.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/context_orchestrator.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/context_window.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/credit_analyzer.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/credits.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/datasources/__init__.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/datasources/base.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/datasources/citations.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/datasources/fpd.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/datasources/odp.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/datasources/patentsview.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/datasources/pfw.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/datasources/ppubs.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/datasources/ptab.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/differ.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/doctor.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/drive.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/editor.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/epistemic/__init__.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/epistemic/belief.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/epistemic/certainty.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/epistemic/failure_graph.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/epistemic/recovery.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/epistemic/stress_tester.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/esdb/__init__.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/esdb/_license.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/esdb/bridge.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/eval/__init__.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/eval/builtins.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/eval/runner.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/executor.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/exporter.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/governance_logic.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/governance_store.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/governance_yaml.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/__init__.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/app.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/main_window.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/session_tab.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/theme.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/widgets/__init__.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/widgets/chat_view.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/widgets/input_bar.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/widgets/provider_bar.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/widgets/token_meter.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/widgets/tool_panel.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/widgets/update_checker.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/worker.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/history_search.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/importer.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/instinct.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/integrations/__init__.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/integrations/agent_skill.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/integrations/aider.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/integrations/base.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/integrations/claude_code.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/integrations/codity.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/integrations/copilot.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/integrations/cursor.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/integrations/gemini.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/integrations/windsurf.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/issue_reporter.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/languages.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/ledger.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/mcp_generator.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/mcp_server.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/migrations/__init__.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/migrations/m001_governance_yaml.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/migrations/m002_agents_slim.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/migrations/m003_compliance_init.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/migrations/m004_ledger_esdb.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/migrations/m005_agent_run_tool.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/migrations/m006_session_governance.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/migrations/runner.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/ollama_cmds.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/patent.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/paths.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/phase.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/plugins.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/profiles.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/rate_limits.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/releaser.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/requirements.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/requirements_parser.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/retrieval.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/safe_write.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/scaffolder.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/serve.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/session.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/session_init.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/session_store.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/__init__.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/ai_agents.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/cloud.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/corporate.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/cross_platform.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/data_engineering.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/devops.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/docs.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/embedded.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/governance.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/hardware.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/mobile.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/platform_engineering.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/productivity.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/software_engineering.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/specsmith_skills.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/ssh.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/web_backend.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills_builder.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/agents.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/community/bug_report.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/community/code_of_conduct.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/community/contributing.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/community/feature_request.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/community/license-Apache-2.0.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/community/license-MIT.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/community/pull_request_template.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/community/security.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/docs/architecture.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/docs/mkdocs.yml.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/docs/readthedocs.yaml.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/docs/requirements.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/docs/test-spec.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/editorconfig.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/gitattributes.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/go/go.mod.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/go/main.go.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/belief-registry.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/context-budget.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/drift-metrics.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/epistemic-axioms.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/failure-modes.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/lifecycle.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/roles.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/rules.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/session-protocol.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/uncertainty-map.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/verification.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/js/package.json.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/ledger.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/python/cli.py.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/python/init.py.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/python/pyproject.toml.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/readme.md.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/rust/Cargo.toml.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/rust/main.rs.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/scripts/exec.cmd.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/scripts/exec.sh.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/scripts/run.cmd.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/scripts/run.sh.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/scripts/setup.cmd.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/scripts/setup.sh.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/workflows/release.yml.j2 +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/tool_installer.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/toolrules.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/tools.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/trace.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/updater.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/upgrader.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/validator.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/vcs/__init__.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/vcs/base.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/vcs/bitbucket.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/vcs/github.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/vcs/gitlab.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/vcs_commands.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/wi_store.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/wireframes.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/workspace.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith.egg-info/dependency_links.txt +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith.egg-info/entry_points.txt +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith.egg-info/top_level.txt +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_CMD_001.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_agent_profiles.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_agent_run_feedback.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_agent_runner_ready.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_ai_client.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_ai_intelligence.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_channel.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_chat_diff_decision.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_chat_runner_openai_compat.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_chat_stdin_protocol.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_cli.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_cli_workflows_history_drive.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_compliance.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_compliance_governance.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_compressor.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_dispatch.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_e2e_nexus.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_endpoints_cli.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_endpoints_store.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_epistemic.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_esdb_license.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_esdb_sqlite.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_fallback_chain.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_importer.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_integrations.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_integrations_codity.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_intelligence.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_issue_reporter.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_mcp_client.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_mcp_server.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_new_modules.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_nexus.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_permissions.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_phase1_4_new.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_phase34_completion.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_rate_limits.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_req_248_262.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_req_358_359.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_skill_marketplace.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_skills_mcp.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_smoke.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_suggester.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_tools.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_validator.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_vcs.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_warp_parity.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_warp_parity_followup.py +0 -0
- {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_wi_lifecycle.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: specsmith
|
|
3
|
-
Version: 0.15.3.
|
|
3
|
+
Version: 0.15.3.dev573
|
|
4
4
|
Summary: AEE governance toolkit for AI-assisted development — session preflight gates, multi-agent dispatch, requirements↔test traceability, ESDB persistence, MCP server, and skills for Warp, Cursor, Claude Code, Copilot, Windsurf, and Aider.
|
|
5
5
|
Author: Layer1Labs Silicon, Inc.
|
|
6
6
|
License-Expression: MIT
|
|
@@ -40,7 +40,7 @@ Requires-Dist: pre-commit>=3.0; extra == "dev"
|
|
|
40
40
|
Requires-Dist: types-pyyaml>=6.0; extra == "dev"
|
|
41
41
|
Requires-Dist: cryptography>=42.0; extra == "dev"
|
|
42
42
|
Provides-Extra: esdb
|
|
43
|
-
Requires-Dist: chronomemory>=0.1.
|
|
43
|
+
Requires-Dist: chronomemory>=0.1.8; extra == "esdb"
|
|
44
44
|
Requires-Dist: cryptography>=42.0; extra == "esdb"
|
|
45
45
|
Provides-Extra: docs
|
|
46
46
|
Requires-Dist: mkdocs>=1.6; extra == "docs"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specsmith"
|
|
7
|
-
version = "0.15.3.
|
|
7
|
+
version = "0.15.3.dev573"
|
|
8
8
|
description = "AEE governance toolkit for AI-assisted development — session preflight gates, multi-agent dispatch, requirements↔test traceability, ESDB persistence, MCP server, and skills for Warp, Cursor, Claude Code, Copilot, Windsurf, and Aider."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -60,7 +60,7 @@ dev = [
|
|
|
60
60
|
# Install: pip install specsmith[esdb]
|
|
61
61
|
# Activate: specsmith esdb enable --key-file /path/to/your.esdb.key
|
|
62
62
|
esdb = [
|
|
63
|
-
"chronomemory>=0.1.
|
|
63
|
+
"chronomemory>=0.1.8", # proprietary commercial — see chronomemory LICENSE
|
|
64
64
|
"cryptography>=42.0",
|
|
65
65
|
]
|
|
66
66
|
docs = [
|
|
@@ -1130,7 +1130,11 @@ def check_industrial_artifacts(root: Path) -> list[AuditResult]:
|
|
|
1130
1130
|
raw = _read_scaffold_raw(root)
|
|
1131
1131
|
declared: dict[str, Any] = raw.get("industrial_artifacts", {}) or {}
|
|
1132
1132
|
declared_eds = declared.get("canopen_eds", []) or []
|
|
1133
|
-
|
|
1133
|
+
# Normalise declared paths to forward slashes and lowercase so they match
|
|
1134
|
+
# on Windows where YAML uses '/' but Path.relative_to() returns '\\' (#251).
|
|
1135
|
+
declared_paths = {
|
|
1136
|
+
e.get("path", "").replace("\\", "/").lower() for e in declared_eds if isinstance(e, dict)
|
|
1137
|
+
}
|
|
1134
1138
|
|
|
1135
1139
|
# Scan for .eds and .xdd files — respect scan_exclude_dirs and
|
|
1136
1140
|
# scan_exclude_patterns from scaffold.yml (#175).
|
|
@@ -1158,9 +1162,12 @@ def check_industrial_artifacts(root: Path) -> list[AuditResult]:
|
|
|
1158
1162
|
if not found_eds:
|
|
1159
1163
|
return results # No industrial artifacts found
|
|
1160
1164
|
|
|
1161
|
-
# Use as_posix() for cross-platform comparison — avoids
|
|
1162
|
-
# mismatch on Windows where str(Path) uses '\' but
|
|
1163
|
-
|
|
1165
|
+
# Use as_posix().lower() for cross-platform comparison — avoids backslash
|
|
1166
|
+
# mismatch on Windows where str(Path) uses '\' but YAML paths use '/' (#173,
|
|
1167
|
+
# #251). Both sides are lowercased so Windows case-insensitive FS is handled.
|
|
1168
|
+
undeclared = [
|
|
1169
|
+
f for f in found_eds if f.relative_to(root).as_posix().lower() not in declared_paths
|
|
1170
|
+
]
|
|
1164
1171
|
|
|
1165
1172
|
if undeclared:
|
|
1166
1173
|
results.append(
|
|
@@ -445,8 +445,17 @@ def _interactive_config(no_git: bool) -> ProjectConfig:
|
|
|
445
445
|
def audit(fix: bool, project_dir: str) -> None:
|
|
446
446
|
"""Run drift and health checks (Section 23 + 26)."""
|
|
447
447
|
from specsmith.auditor import run_audit
|
|
448
|
+
from specsmith.sync import auto_migrate_if_needed
|
|
448
449
|
|
|
449
450
|
root = Path(project_dir).resolve()
|
|
451
|
+
auto_counts = auto_migrate_if_needed(root)
|
|
452
|
+
if auto_counts:
|
|
453
|
+
console.print(
|
|
454
|
+
" [cyan]⟳[/cyan] ESDB auto-migrate: "
|
|
455
|
+
f"{auto_counts.get('requirements', 0)} requirements + "
|
|
456
|
+
f"{auto_counts.get('testcases', 0)} testcases "
|
|
457
|
+
f"({auto_counts.get('skipped', 0)} skipped)"
|
|
458
|
+
)
|
|
450
459
|
console.print(f"[bold]Auditing[/bold] {root}\n")
|
|
451
460
|
report = run_audit(root)
|
|
452
461
|
|
|
@@ -1489,14 +1498,20 @@ def diff(project_dir: str, html_output: str | None) -> None:
|
|
|
1489
1498
|
default=False,
|
|
1490
1499
|
help="Run the new-user onboarding checklist (REQ-127).",
|
|
1491
1500
|
)
|
|
1492
|
-
|
|
1501
|
+
@click.option(
|
|
1502
|
+
"--json",
|
|
1503
|
+
"as_json",
|
|
1504
|
+
is_flag=True,
|
|
1505
|
+
default=False,
|
|
1506
|
+
help="Emit machine-readable health report JSON.",
|
|
1507
|
+
)
|
|
1508
|
+
def doctor(project_dir: str, onboarding: bool, as_json: bool) -> None:
|
|
1493
1509
|
"""Check if verification tools are installed locally.
|
|
1494
1510
|
|
|
1495
1511
|
With --onboarding, walks through a 6-step checklist for new users that
|
|
1496
1512
|
confirms scaffold.yml, governance files, agent provider setup, optional
|
|
1497
1513
|
Nexus broker availability, and prints next-step commands (REQ-127).
|
|
1498
1514
|
"""
|
|
1499
|
-
from specsmith.doctor import run_doctor
|
|
1500
1515
|
|
|
1501
1516
|
root = Path(project_dir).resolve()
|
|
1502
1517
|
|
|
@@ -1572,28 +1587,108 @@ def doctor(project_dir: str, onboarding: bool) -> None:
|
|
|
1572
1587
|
)
|
|
1573
1588
|
return
|
|
1574
1589
|
|
|
1575
|
-
|
|
1590
|
+
import importlib.metadata
|
|
1591
|
+
import json as _json
|
|
1592
|
+
import shutil
|
|
1593
|
+
import subprocess
|
|
1594
|
+
import sys
|
|
1576
1595
|
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1596
|
+
from specsmith.auditor import run_audit
|
|
1597
|
+
from specsmith.esdb import ESDB_BACKEND, open_default_store
|
|
1598
|
+
from specsmith.phase import read_phase
|
|
1580
1599
|
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
if
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1600
|
+
def _tool_version(cmd: str) -> tuple[bool, str]:
|
|
1601
|
+
exe = shutil.which(cmd)
|
|
1602
|
+
if not exe:
|
|
1603
|
+
return False, "not found"
|
|
1604
|
+
try:
|
|
1605
|
+
proc = subprocess.run(
|
|
1606
|
+
[exe, "--version"],
|
|
1607
|
+
capture_output=True,
|
|
1608
|
+
text=True,
|
|
1609
|
+
timeout=5,
|
|
1610
|
+
check=False,
|
|
1611
|
+
)
|
|
1612
|
+
out = (proc.stdout or proc.stderr or "").strip().splitlines()
|
|
1613
|
+
if out:
|
|
1614
|
+
return True, out[0]
|
|
1615
|
+
return True, "installed"
|
|
1616
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
1617
|
+
return True, "installed"
|
|
1618
|
+
|
|
1619
|
+
checks: list[dict[str, str]] = []
|
|
1620
|
+
|
|
1621
|
+
def _add(name: str, passed: bool, detail: str, warn: bool = False) -> None:
|
|
1622
|
+
status = "warn" if warn else ("pass" if passed else "fail")
|
|
1623
|
+
checks.append({"name": name, "status": status, "detail": detail})
|
|
1624
|
+
|
|
1625
|
+
py_ok = sys.version_info >= (3, 10)
|
|
1626
|
+
_add(
|
|
1627
|
+
"python",
|
|
1628
|
+
py_ok,
|
|
1629
|
+
f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
|
|
1630
|
+
)
|
|
1588
1631
|
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1632
|
+
git_ok, git_detail = _tool_version("git")
|
|
1633
|
+
_add("git", git_ok, git_detail)
|
|
1634
|
+
|
|
1635
|
+
_add("specsmith", True, __version__)
|
|
1636
|
+
|
|
1637
|
+
try:
|
|
1638
|
+
chrono_ver = importlib.metadata.version("chronomemory")
|
|
1639
|
+
_add("chronomemory", True, chrono_ver)
|
|
1640
|
+
except importlib.metadata.PackageNotFoundError:
|
|
1641
|
+
_add("chronomemory", False, "not installed")
|
|
1642
|
+
|
|
1643
|
+
esdb_open = False
|
|
1644
|
+
chain_ok = False
|
|
1645
|
+
record_count = 0
|
|
1646
|
+
try:
|
|
1647
|
+
with open_default_store(root, warn=False) as store: # type: ignore[attr-defined]
|
|
1648
|
+
esdb_open = True
|
|
1649
|
+
record_count = int(store.record_count())
|
|
1650
|
+
chain_ok = store.chain_valid() is not False
|
|
1651
|
+
except Exception as exc: # noqa: BLE001
|
|
1652
|
+
_add("esdb backend", False, f"open failed: {exc}")
|
|
1653
|
+
if esdb_open:
|
|
1654
|
+
_add("esdb backend", True, f"{ESDB_BACKEND} open")
|
|
1655
|
+
_add(
|
|
1656
|
+
"esdb chain",
|
|
1657
|
+
chain_ok,
|
|
1658
|
+
f"{'valid' if chain_ok else 'invalid'} ({record_count} records)",
|
|
1596
1659
|
)
|
|
1660
|
+
_add("esdb record count", True, str(record_count))
|
|
1661
|
+
|
|
1662
|
+
audit_report = run_audit(root)
|
|
1663
|
+
_add(
|
|
1664
|
+
"audit",
|
|
1665
|
+
audit_report.healthy,
|
|
1666
|
+
"clean" if audit_report.healthy else f"{audit_report.failed} issue(s)",
|
|
1667
|
+
)
|
|
1668
|
+
|
|
1669
|
+
phase_key = read_phase(root)
|
|
1670
|
+
_add("phase", True, phase_key)
|
|
1671
|
+
|
|
1672
|
+
for tool in ("ruff", "pytest", "pipx"):
|
|
1673
|
+
ok, detail = _tool_version(tool)
|
|
1674
|
+
_add(tool, ok, detail)
|
|
1675
|
+
|
|
1676
|
+
overall = "pass" if all(c["status"] != "fail" for c in checks) else "fail"
|
|
1677
|
+
|
|
1678
|
+
if as_json:
|
|
1679
|
+
click.echo(_json.dumps({"checks": checks, "overall": overall}, indent=2))
|
|
1680
|
+
return
|
|
1681
|
+
|
|
1682
|
+
icon = {"pass": "✅", "fail": "❌", "warn": "⚠️"}
|
|
1683
|
+
console.print("specsmith doctor")
|
|
1684
|
+
console.print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
|
1685
|
+
for c in checks:
|
|
1686
|
+
console.print(f" {c['name']:<14} {icon[c['status']]} {c['detail']}")
|
|
1687
|
+
console.print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
|
1688
|
+
if overall == "pass":
|
|
1689
|
+
console.print("All checks passed.")
|
|
1690
|
+
else:
|
|
1691
|
+
console.print("One or more checks failed.")
|
|
1597
1692
|
|
|
1598
1693
|
|
|
1599
1694
|
@main.command()
|
|
@@ -2577,30 +2672,47 @@ def save_cmd(project_dir: str, message: str, no_push: bool, force: bool, as_json
|
|
|
2577
2672
|
"""
|
|
2578
2673
|
import json as _json
|
|
2579
2674
|
|
|
2580
|
-
from specsmith
|
|
2675
|
+
from specsmith import esdb as esdb_mod
|
|
2676
|
+
from specsmith.sync import normalize_esdb_gitignore_policy
|
|
2581
2677
|
from specsmith.vcs_commands import has_uncommitted_changes, run_commit, run_push
|
|
2582
2678
|
|
|
2583
2679
|
root = Path(project_dir).resolve()
|
|
2584
2680
|
steps: list[dict[str, Any]] = []
|
|
2681
|
+
# 0. Normalize ESDB policy for legacy projects
|
|
2682
|
+
try:
|
|
2683
|
+
changed = normalize_esdb_gitignore_policy(root)
|
|
2684
|
+
steps.append(
|
|
2685
|
+
{
|
|
2686
|
+
"step": "gitignore_policy",
|
|
2687
|
+
"ok": True,
|
|
2688
|
+
"note": "normalized legacy ESDB policy" if changed else "already compliant",
|
|
2689
|
+
}
|
|
2690
|
+
)
|
|
2691
|
+
except Exception as exc: # noqa: BLE001
|
|
2692
|
+
steps.append({"step": "gitignore_policy", "ok": False, "error": str(exc)})
|
|
2585
2693
|
|
|
2586
2694
|
# 1. ESDB backup
|
|
2587
2695
|
try:
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2696
|
+
with esdb_mod.open_default_store(root, warn=False) as store: # type: ignore[attr-defined]
|
|
2697
|
+
backup_fn = getattr(store, "backup", None)
|
|
2698
|
+
if callable(backup_fn):
|
|
2699
|
+
backup_path = backup_fn()
|
|
2700
|
+
steps.append(
|
|
2701
|
+
{
|
|
2702
|
+
"step": "esdb_backup",
|
|
2703
|
+
"ok": True,
|
|
2704
|
+
"path": str(backup_path),
|
|
2705
|
+
"backend": esdb_mod.ESDB_BACKEND,
|
|
2706
|
+
}
|
|
2707
|
+
)
|
|
2708
|
+
else:
|
|
2709
|
+
steps.append(
|
|
2710
|
+
{
|
|
2711
|
+
"step": "esdb_backup",
|
|
2712
|
+
"ok": True,
|
|
2713
|
+
"note": f"{esdb_mod.ESDB_BACKEND} backend has no native backup method",
|
|
2714
|
+
}
|
|
2715
|
+
)
|
|
2604
2716
|
except Exception as exc: # noqa: BLE001
|
|
2605
2717
|
steps.append({"step": "esdb_backup", "ok": False, "error": str(exc)})
|
|
2606
2718
|
|
|
@@ -5653,7 +5765,8 @@ def phase_next(project_dir: str, force: bool) -> None:
|
|
|
5653
5765
|
"""Advance to the next AEE workflow phase.
|
|
5654
5766
|
|
|
5655
5767
|
Performs readiness checks on the current phase first.
|
|
5656
|
-
Use --force to skip checks.
|
|
5768
|
+
Use --force to skip checks. Phases listed in ``suppressed_phases`` in the
|
|
5769
|
+
scaffold config (docs/SPECSMITH.yml) are automatically skipped (#254).
|
|
5657
5770
|
"""
|
|
5658
5771
|
from specsmith.phase import PHASE_MAP, evaluate_phase, read_phase, write_phase
|
|
5659
5772
|
|
|
@@ -5679,12 +5792,45 @@ def phase_next(project_dir: str, force: bool) -> None:
|
|
|
5679
5792
|
)
|
|
5680
5793
|
return
|
|
5681
5794
|
|
|
5682
|
-
|
|
5683
|
-
|
|
5795
|
+
# Read suppressed_phases from the scaffold config (#254).
|
|
5796
|
+
suppressed: list[str] = []
|
|
5797
|
+
try:
|
|
5798
|
+
import yaml as _yaml
|
|
5799
|
+
|
|
5800
|
+
from specsmith.paths import find_scaffold
|
|
5801
|
+
|
|
5802
|
+
sp = find_scaffold(root)
|
|
5803
|
+
if sp:
|
|
5804
|
+
_raw = _yaml.safe_load(sp.read_text(encoding="utf-8")) or {}
|
|
5805
|
+
suppressed = [str(p) for p in (_raw.get("suppressed_phases") or [])]
|
|
5806
|
+
except Exception: # noqa: BLE001
|
|
5807
|
+
pass # Never block phase advance on config parse errors
|
|
5808
|
+
|
|
5809
|
+
# Walk the phase chain until we find a non-suppressed next phase.
|
|
5810
|
+
target_key: str | None = phase.next_phase
|
|
5811
|
+
skipped: list[str] = []
|
|
5812
|
+
while target_key and target_key in suppressed:
|
|
5813
|
+
skipped.append(target_key)
|
|
5814
|
+
target_phase = PHASE_MAP.get(target_key)
|
|
5815
|
+
target_key = target_phase.next_phase if target_phase else None
|
|
5816
|
+
|
|
5817
|
+
if not target_key:
|
|
5818
|
+
console.print(
|
|
5819
|
+
f"[bold]{phase.emoji} {phase.label}[/bold] is the effective final phase "
|
|
5820
|
+
"(all remaining phases are suppressed)."
|
|
5821
|
+
)
|
|
5822
|
+
if skipped:
|
|
5823
|
+
console.print(f" [dim]Suppressed: {', '.join(skipped)}[/dim]")
|
|
5824
|
+
return
|
|
5825
|
+
|
|
5826
|
+
write_phase(root, target_key)
|
|
5827
|
+
next_phase = PHASE_MAP[target_key]
|
|
5684
5828
|
console.print(
|
|
5685
5829
|
f"[green]\u2713[/green] Advanced from [bold]{phase.label}[/bold] "
|
|
5686
5830
|
f"to [bold]{next_phase.emoji} {next_phase.label}[/bold]."
|
|
5687
5831
|
)
|
|
5832
|
+
if skipped:
|
|
5833
|
+
console.print(f" [dim]Skipped suppressed phase(s): {', '.join(skipped)}[/dim]")
|
|
5688
5834
|
console.print(f" {next_phase.description}")
|
|
5689
5835
|
if next_phase.commands:
|
|
5690
5836
|
console.print("\n [bold]Next steps:[/bold]")
|
|
@@ -5693,14 +5839,14 @@ def phase_next(project_dir: str, force: bool) -> None:
|
|
|
5693
5839
|
|
|
5694
5840
|
# G3: keep the agents routing table aligned with the active phase.
|
|
5695
5841
|
# We pin a synthetic ``phase:active`` route so the runner can flip the
|
|
5696
|
-
# whole session to the new phase
|
|
5842
|
+
# whole session to the new phase's preferred profile without the user
|
|
5697
5843
|
# having to run `specsmith agents route set` themselves.
|
|
5698
5844
|
try:
|
|
5699
5845
|
from specsmith.agent.profiles import ProfileStore
|
|
5700
5846
|
|
|
5701
5847
|
agents_store = ProfileStore.load()
|
|
5702
5848
|
if agents_store.profiles:
|
|
5703
|
-
phase_key_target = f"phase:{
|
|
5849
|
+
phase_key_target = f"phase:{target_key}"
|
|
5704
5850
|
target_id = agents_store.routes.get(phase_key_target) or (
|
|
5705
5851
|
agents_store.default_profile_id
|
|
5706
5852
|
)
|
|
@@ -5968,10 +6114,11 @@ def sync_cmd(project_dir: str, check_only: bool, as_json: bool) -> None:
|
|
|
5968
6114
|
"""
|
|
5969
6115
|
import json as _json
|
|
5970
6116
|
|
|
5971
|
-
from specsmith.sync import run_sync
|
|
6117
|
+
from specsmith.sync import auto_migrate_if_needed, run_sync
|
|
5972
6118
|
|
|
5973
6119
|
root = Path(project_dir).resolve()
|
|
5974
6120
|
result = run_sync(root, dry_run=check_only)
|
|
6121
|
+
auto_counts = {} if check_only else auto_migrate_if_needed(root)
|
|
5975
6122
|
|
|
5976
6123
|
if as_json:
|
|
5977
6124
|
click.echo(
|
|
@@ -5985,6 +6132,8 @@ def sync_cmd(project_dir: str, check_only: bool, as_json: bool) -> None:
|
|
|
5985
6132
|
"tests_changed": result.tests_changed,
|
|
5986
6133
|
"in_sync": not result.changed,
|
|
5987
6134
|
"dry_run": result.dry_run,
|
|
6135
|
+
"auto_migrated": bool(auto_counts),
|
|
6136
|
+
"auto_migrate_counts": auto_counts,
|
|
5988
6137
|
},
|
|
5989
6138
|
indent=2,
|
|
5990
6139
|
)
|
|
@@ -5998,6 +6147,13 @@ def sync_cmd(project_dir: str, check_only: bool, as_json: bool) -> None:
|
|
|
5998
6147
|
console.print(f"[green]\u2713[/green] {result.message}")
|
|
5999
6148
|
else:
|
|
6000
6149
|
console.print("[green]\u2713[/green] Machine state already in sync.")
|
|
6150
|
+
if auto_counts:
|
|
6151
|
+
console.print(
|
|
6152
|
+
" [cyan]⟳[/cyan] ESDB auto-migrate: "
|
|
6153
|
+
f"{auto_counts.get('requirements', 0)} requirements + "
|
|
6154
|
+
f"{auto_counts.get('testcases', 0)} testcases "
|
|
6155
|
+
f"({auto_counts.get('skipped', 0)} skipped)"
|
|
6156
|
+
)
|
|
6001
6157
|
|
|
6002
6158
|
if check_only and result.changed:
|
|
6003
6159
|
raise SystemExit(1)
|
|
@@ -10424,8 +10580,10 @@ def esdb_status_cmd(project_dir: str, as_json: bool) -> None:
|
|
|
10424
10580
|
|
|
10425
10581
|
from specsmith.esdb import CHRONO_AVAILABLE, open_default_store
|
|
10426
10582
|
from specsmith.esdb._license import check_license, resolve_license_path
|
|
10583
|
+
from specsmith.sync import auto_migrate_if_needed
|
|
10427
10584
|
|
|
10428
10585
|
root = Path(project_dir).resolve()
|
|
10586
|
+
auto_counts = auto_migrate_if_needed(root)
|
|
10429
10587
|
|
|
10430
10588
|
# Determine license status without side-effect warnings
|
|
10431
10589
|
lic_path = resolve_license_path()
|
|
@@ -10491,6 +10649,8 @@ def esdb_status_cmd(project_dir: str, as_json: bool) -> None:
|
|
|
10491
10649
|
"chain_valid": chain_ok,
|
|
10492
10650
|
"counts": counts,
|
|
10493
10651
|
"license": license_info,
|
|
10652
|
+
"auto_migrated": bool(auto_counts),
|
|
10653
|
+
"auto_migrate_counts": auto_counts,
|
|
10494
10654
|
},
|
|
10495
10655
|
indent=2,
|
|
10496
10656
|
)
|
|
@@ -10521,6 +10681,13 @@ def esdb_status_cmd(project_dir: str, as_json: bool) -> None:
|
|
|
10521
10681
|
console.print(
|
|
10522
10682
|
" [dim]Use 'specsmith esdb enable --key-file <path>' to activate ChronoStore.[/dim]"
|
|
10523
10683
|
)
|
|
10684
|
+
if auto_counts:
|
|
10685
|
+
console.print(
|
|
10686
|
+
" [cyan]⟳[/cyan] Auto-migrated from legacy JSON: "
|
|
10687
|
+
f"{auto_counts.get('requirements', 0)} requirements + "
|
|
10688
|
+
f"{auto_counts.get('testcases', 0)} testcases "
|
|
10689
|
+
f"({auto_counts.get('skipped', 0)} skipped)"
|
|
10690
|
+
)
|
|
10524
10691
|
|
|
10525
10692
|
|
|
10526
10693
|
@esdb_group.command(name="enable")
|
|
@@ -10554,9 +10721,15 @@ def esdb_enable_cmd(key_file: str, as_json: bool) -> None:
|
|
|
10554
10721
|
console.print("[dim]Contact licensing@layer1labs.com to obtain a valid license.[/dim]")
|
|
10555
10722
|
raise SystemExit(1)
|
|
10556
10723
|
|
|
10724
|
+
import os
|
|
10725
|
+
|
|
10557
10726
|
dest = Path.home() / ".specsmith" / "esdb.key"
|
|
10558
10727
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
10559
|
-
|
|
10728
|
+
# Skip the copy when source and destination are the same file (#249).
|
|
10729
|
+
# On Windows, shutil.copy2(src, dst) raises PermissionError / WinError 32
|
|
10730
|
+
# when src == dst (file is already at the expected location).
|
|
10731
|
+
if os.path.realpath(str(key_file)) != os.path.realpath(str(dest)):
|
|
10732
|
+
shutil.copy2(key_file, dest)
|
|
10560
10733
|
|
|
10561
10734
|
if as_json:
|
|
10562
10735
|
click.echo(
|
|
@@ -289,6 +289,22 @@ class SqliteStore:
|
|
|
289
289
|
row = conn.execute("SELECT MAX(rowid) FROM records").fetchone()
|
|
290
290
|
return int(row[0]) if row and row[0] is not None else 0
|
|
291
291
|
|
|
292
|
+
def backup(self, backup_dir: str | Path | None = None) -> Path:
|
|
293
|
+
"""Create a timestamped SQLite snapshot and return its path."""
|
|
294
|
+
conn = self._require_open()
|
|
295
|
+
ts = time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())
|
|
296
|
+
dest_dir = Path(backup_dir) if backup_dir else self._db_path.parent / "backups"
|
|
297
|
+
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
298
|
+
dest = dest_dir / f"esdb_sqlite_backup_{ts}.sqlite3"
|
|
299
|
+
|
|
300
|
+
backup_conn = sqlite3.connect(str(dest))
|
|
301
|
+
try:
|
|
302
|
+
conn.backup(backup_conn)
|
|
303
|
+
finally:
|
|
304
|
+
backup_conn.close()
|
|
305
|
+
|
|
306
|
+
return dest
|
|
307
|
+
|
|
292
308
|
def chain_valid(self) -> bool:
|
|
293
309
|
"""Always True — SQLite ACID guarantees integrity (no SHA-256 WAL chain)."""
|
|
294
310
|
return True
|
|
@@ -207,6 +207,88 @@ class SyncResult:
|
|
|
207
207
|
return "Machine state already in sync."
|
|
208
208
|
|
|
209
209
|
|
|
210
|
+
# ---------------------------------------------------------------------------
|
|
211
|
+
# ESDB legacy policy normalization (.gitignore)
|
|
212
|
+
# ---------------------------------------------------------------------------
|
|
213
|
+
|
|
214
|
+
_ESDB_GITIGNORE_FORBIDDEN = frozenset(
|
|
215
|
+
{
|
|
216
|
+
".specsmith/",
|
|
217
|
+
".chronomemory/",
|
|
218
|
+
}
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
_ESDB_GITIGNORE_REQUIRED = (
|
|
222
|
+
"!.specsmith/config.yml",
|
|
223
|
+
"!.specsmith/requirements.json",
|
|
224
|
+
"!.specsmith/testcases.json",
|
|
225
|
+
"!.specsmith/esdb.sqlite3",
|
|
226
|
+
"!.chronomemory/events.wal",
|
|
227
|
+
"!.chronomemory/snapshot.json",
|
|
228
|
+
".chronomemory/backup/",
|
|
229
|
+
".specsmith/workitems.json",
|
|
230
|
+
".specsmith/runs/",
|
|
231
|
+
".specsmith/chat/",
|
|
232
|
+
".specsmith/perf/",
|
|
233
|
+
".specsmith/recovery/",
|
|
234
|
+
".specsmith/ledger.jsonl",
|
|
235
|
+
".specsmith/ledger-chain.txt",
|
|
236
|
+
".specsmith/trace.jsonl",
|
|
237
|
+
".specsmith/credit-budget.json",
|
|
238
|
+
".specsmith/credits.json",
|
|
239
|
+
".specsmith/model-rate-limits.json",
|
|
240
|
+
".specsmith/pids/",
|
|
241
|
+
".specsmith/logs/",
|
|
242
|
+
".specsmith/sessions/",
|
|
243
|
+
".specsmith/agent-reports/",
|
|
244
|
+
".specsmith/dispatch/",
|
|
245
|
+
".specsmith/esdb_migration_manifest.json",
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def normalize_esdb_gitignore_policy(root: Path, *, dry_run: bool = False) -> bool:
|
|
250
|
+
"""Normalize ESDB gitignore policy for legacy projects.
|
|
251
|
+
|
|
252
|
+
Ensures broad ignores that hide canonical ESDB state are removed and that
|
|
253
|
+
explicit policy lines are present so SQLite/Chrono canonical artifacts are
|
|
254
|
+
always tracked while runtime files stay ignored.
|
|
255
|
+
"""
|
|
256
|
+
gitignore_path = root / ".gitignore"
|
|
257
|
+
if gitignore_path.exists():
|
|
258
|
+
original_lines = gitignore_path.read_text(encoding="utf-8").splitlines()
|
|
259
|
+
else:
|
|
260
|
+
original_lines = []
|
|
261
|
+
|
|
262
|
+
normalized_lines: list[str] = []
|
|
263
|
+
removed_forbidden = False
|
|
264
|
+
for line in original_lines:
|
|
265
|
+
if line.strip() in _ESDB_GITIGNORE_FORBIDDEN:
|
|
266
|
+
removed_forbidden = True
|
|
267
|
+
continue
|
|
268
|
+
normalized_lines.append(line)
|
|
269
|
+
|
|
270
|
+
existing = {line.strip() for line in normalized_lines if line.strip()}
|
|
271
|
+
missing = [entry for entry in _ESDB_GITIGNORE_REQUIRED if entry not in existing]
|
|
272
|
+
changed = removed_forbidden or bool(missing)
|
|
273
|
+
|
|
274
|
+
if not changed:
|
|
275
|
+
return False
|
|
276
|
+
|
|
277
|
+
if normalized_lines and normalized_lines[-1].strip():
|
|
278
|
+
normalized_lines.append("")
|
|
279
|
+
if missing:
|
|
280
|
+
normalized_lines.append("# specsmith ESDB policy (auto-normalized)")
|
|
281
|
+
normalized_lines.extend(missing)
|
|
282
|
+
|
|
283
|
+
if not dry_run:
|
|
284
|
+
rendered = "\n".join(normalized_lines)
|
|
285
|
+
if rendered and not rendered.endswith("\n"):
|
|
286
|
+
rendered += "\n"
|
|
287
|
+
gitignore_path.write_text(rendered, encoding="utf-8")
|
|
288
|
+
|
|
289
|
+
return True
|
|
290
|
+
|
|
291
|
+
|
|
210
292
|
# ---------------------------------------------------------------------------
|
|
211
293
|
# Core sync
|
|
212
294
|
# ---------------------------------------------------------------------------
|
|
@@ -244,6 +326,8 @@ def run_sync(root: Path, *, dry_run: bool = False) -> SyncResult:
|
|
|
244
326
|
tests_md_path = root / "docs" / "TESTS.md"
|
|
245
327
|
reqs_json_path = state_dir / "requirements.json"
|
|
246
328
|
tests_json_path = state_dir / "testcases.json"
|
|
329
|
+
if not dry_run:
|
|
330
|
+
normalize_esdb_gitignore_policy(root)
|
|
247
331
|
|
|
248
332
|
if is_yaml_mode(root):
|
|
249
333
|
# ── YAML-first mode ─────────────────────────────────────────────────
|
|
@@ -469,6 +553,65 @@ def _sync_esdb(root: Path, state_dir: Path) -> None:
|
|
|
469
553
|
pass
|
|
470
554
|
|
|
471
555
|
|
|
556
|
+
def _json_file_has_content(path: Path) -> bool:
|
|
557
|
+
"""Return True when *path* exists and contains a non-empty JSON list.
|
|
558
|
+
|
|
559
|
+
Falls back to a small size heuristic for malformed JSON so we still treat
|
|
560
|
+
legacy state files with obvious content as migratable.
|
|
561
|
+
"""
|
|
562
|
+
if not path.is_file():
|
|
563
|
+
return False
|
|
564
|
+
try:
|
|
565
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
566
|
+
return isinstance(payload, list) and len(payload) > 0
|
|
567
|
+
except (OSError, ValueError):
|
|
568
|
+
with contextlib.suppress(OSError):
|
|
569
|
+
return path.stat().st_size > 2
|
|
570
|
+
return False
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def _should_auto_migrate(store: Any, specsmith_dir: Path) -> bool:
|
|
574
|
+
"""Return True when ESDB is empty and legacy JSON sources have content.
|
|
575
|
+
|
|
576
|
+
This is used by CLI surfaces (`sync`, `audit`, `esdb status`) to
|
|
577
|
+
opportunistically bootstrap ESDB from `.specsmith/*.json` exactly once.
|
|
578
|
+
"""
|
|
579
|
+
if store.record_count() > 0:
|
|
580
|
+
return False
|
|
581
|
+
candidates = (
|
|
582
|
+
specsmith_dir / "requirements.json",
|
|
583
|
+
specsmith_dir / "testcases.json",
|
|
584
|
+
)
|
|
585
|
+
return any(_json_file_has_content(path) for path in candidates)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def auto_migrate_if_needed(root: Path) -> dict[str, int]:
|
|
589
|
+
"""Best-effort ESDB bootstrap from legacy JSON when the store is empty.
|
|
590
|
+
|
|
591
|
+
Returns migration counts on success, or an empty dict when no migration was
|
|
592
|
+
needed or if any backend error occurred.
|
|
593
|
+
"""
|
|
594
|
+
from specsmith.esdb import open_default_store
|
|
595
|
+
|
|
596
|
+
specsmith_dir = root / ".specsmith"
|
|
597
|
+
if not specsmith_dir.exists():
|
|
598
|
+
return {}
|
|
599
|
+
try:
|
|
600
|
+
with open_default_store(root, warn=False) as store: # type: ignore[attr-defined]
|
|
601
|
+
if not _should_auto_migrate(store, specsmith_dir):
|
|
602
|
+
return {}
|
|
603
|
+
counts = store.migrate_from_json(specsmith_dir)
|
|
604
|
+
if isinstance(counts, dict):
|
|
605
|
+
return {
|
|
606
|
+
"requirements": int(counts.get("requirements", 0)),
|
|
607
|
+
"testcases": int(counts.get("testcases", 0)),
|
|
608
|
+
"skipped": int(counts.get("skipped", 0)),
|
|
609
|
+
}
|
|
610
|
+
return {}
|
|
611
|
+
except Exception: # noqa: BLE001 - never block caller commands
|
|
612
|
+
return {}
|
|
613
|
+
|
|
614
|
+
|
|
472
615
|
def check_sync(root: Path) -> SyncResult:
|
|
473
616
|
"""Check whether machine state is in sync without writing anything.
|
|
474
617
|
|
|
@@ -45,6 +45,13 @@ htmlcov/
|
|
|
45
45
|
# - .specsmith/esdb.sqlite3
|
|
46
46
|
# - .chronomemory/events.wal
|
|
47
47
|
# - .chronomemory/snapshot.json
|
|
48
|
+
# Always track these even if broader ignore rules are added later:
|
|
49
|
+
!.specsmith/config.yml
|
|
50
|
+
!.specsmith/requirements.json
|
|
51
|
+
!.specsmith/testcases.json
|
|
52
|
+
!.specsmith/esdb.sqlite3
|
|
53
|
+
!.chronomemory/events.wal
|
|
54
|
+
!.chronomemory/snapshot.json
|
|
48
55
|
#
|
|
49
56
|
# Ignore runtime-only / regeneratable artifacts:
|
|
50
57
|
.chronomemory/backup/
|