specsmith 0.5.0.dev224__tar.gz → 0.5.0.dev225__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.5.0.dev224/src/specsmith.egg-info → specsmith-0.5.0.dev225}/PKG-INFO +1 -1
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/pyproject.toml +1 -1
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/cli.py +409 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225/src/specsmith.egg-info}/PKG-INFO +1 -1
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith.egg-info/SOURCES.txt +1 -0
- specsmith-0.5.0.dev225/tests/test_cli_workflows_history_drive.py +281 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/LICENSE +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/README.md +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/setup.cfg +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/epistemic/__init__.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/epistemic/belief.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/epistemic/certainty.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/epistemic/failure_graph.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/epistemic/py.typed +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/epistemic/recovery.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/epistemic/session.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/epistemic/stress_tester.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/epistemic/trace.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/__init__.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/__main__.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/__init__.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/broker.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/cleanup.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/events.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/indexer.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/mcp.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/memory.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/orchestrator.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/repl.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/router.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/rules.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/safety.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/tools.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/verifier.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/architect.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/auditor.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/auth.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/commands/__init__.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/compressor.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/config.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/console_utils.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/credit_analyzer.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/credits.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/differ.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/doctor.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/epistemic/__init__.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/epistemic/belief.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/epistemic/certainty.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/epistemic/failure_graph.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/epistemic/recovery.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/epistemic/stress_tester.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/executor.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/exporter.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/__init__.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/app.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/main_window.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/session_tab.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/theme.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/widgets/__init__.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/widgets/chat_view.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/widgets/input_bar.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/widgets/provider_bar.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/widgets/token_meter.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/widgets/tool_panel.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/widgets/update_checker.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/worker.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/importer.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/integrations/__init__.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/integrations/agent_skill.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/integrations/aider.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/integrations/base.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/integrations/claude_code.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/integrations/copilot.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/integrations/cursor.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/integrations/gemini.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/integrations/windsurf.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/languages.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/ledger.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/patent.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/phase.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/plugins.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/profiles.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/rate_limits.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/releaser.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/requirements.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/requirements_parser.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/retrieval.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/scaffolder.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/serve.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/session.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/agents.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/community/bug_report.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/community/code_of_conduct.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/community/contributing.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/community/feature_request.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/community/license-Apache-2.0.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/community/license-MIT.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/community/pull_request_template.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/community/security.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/docs/architecture.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/docs/mkdocs.yml.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/docs/readthedocs.yaml.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/docs/requirements.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/docs/test-spec.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/editorconfig.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/gitattributes.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/gitignore.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/go/go.mod.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/go/main.go.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/belief-registry.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/context-budget.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/drift-metrics.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/epistemic-axioms.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/failure-modes.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/lifecycle.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/roles.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/rules.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/session-protocol.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/uncertainty-map.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/verification.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/js/package.json.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/ledger.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/python/cli.py.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/python/init.py.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/python/pyproject.toml.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/readme.md.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/rust/Cargo.toml.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/rust/main.rs.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/scripts/exec.cmd.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/scripts/exec.sh.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/scripts/run.cmd.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/scripts/run.sh.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/scripts/setup.cmd.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/scripts/setup.sh.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/workflows/release.yml.j2 +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/tool_installer.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/toolrules.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/tools.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/trace.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/updater.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/upgrader.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/validator.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/vcs/__init__.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/vcs/base.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/vcs/bitbucket.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/vcs/github.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/vcs/gitlab.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/vcs_commands.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/wireframes.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/workspace.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith.egg-info/dependency_links.txt +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith.egg-info/entry_points.txt +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith.egg-info/requires.txt +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith.egg-info/top_level.txt +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_CMD_001.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_auditor.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_cli.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_compressor.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_e2e_nexus.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_epistemic.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_importer.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_integrations.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_nexus.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_phase1_4_new.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_rate_limits.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_scaffolder.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_smoke.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_tools.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_validator.py +0 -0
- {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_vcs.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: specsmith
|
|
3
|
-
Version: 0.5.0.
|
|
3
|
+
Version: 0.5.0.dev225
|
|
4
4
|
Summary: Applied Epistemic Engineering toolkit — AEE agent sessions, execution profiles, FPGA/HDL governance, tool installer, 50+ CLI commands.
|
|
5
5
|
Author: BitConcepts
|
|
6
6
|
License-Expression: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specsmith"
|
|
7
|
-
version = "0.5.0.
|
|
7
|
+
version = "0.5.0.dev225"
|
|
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"
|
|
@@ -5532,6 +5532,415 @@ def cloud_spawn(utterance: str, project_dir: str, endpoint: str, dry_run: bool)
|
|
|
5532
5532
|
main.add_command(cloud_group)
|
|
5533
5533
|
|
|
5534
5534
|
|
|
5535
|
+
# ---------------------------------------------------------------------------
|
|
5536
|
+
# Workflow — parameterised command snippets (Warp-style Workflows)
|
|
5537
|
+
# ---------------------------------------------------------------------------
|
|
5538
|
+
|
|
5539
|
+
|
|
5540
|
+
@main.group(name="workflow")
|
|
5541
|
+
def workflow_group() -> None:
|
|
5542
|
+
"""Record, list, and run parameterised command snippets.
|
|
5543
|
+
|
|
5544
|
+
Workflows are saved as YAML files under `.specsmith/workflows/<name>.yml`.
|
|
5545
|
+
Each workflow has a name, an optional description, a command template
|
|
5546
|
+
that may contain ``{{ param }}`` placeholders, and a list of accepted
|
|
5547
|
+
params. ``specsmith workflow run <name>`` substitutes the params and
|
|
5548
|
+
executes the resulting command via ``subprocess.run``.
|
|
5549
|
+
"""
|
|
5550
|
+
|
|
5551
|
+
|
|
5552
|
+
def _workflows_dir(root: Path) -> Path:
|
|
5553
|
+
d = root / ".specsmith" / "workflows"
|
|
5554
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
5555
|
+
return d
|
|
5556
|
+
|
|
5557
|
+
|
|
5558
|
+
@workflow_group.command(name="record")
|
|
5559
|
+
@click.argument("name")
|
|
5560
|
+
@click.option(
|
|
5561
|
+
"--command",
|
|
5562
|
+
"command",
|
|
5563
|
+
required=True,
|
|
5564
|
+
help="Command template. Use {{ param }} for substitution placeholders.",
|
|
5565
|
+
)
|
|
5566
|
+
@click.option("--description", "description", default="", help="Free-text description.")
|
|
5567
|
+
@click.option(
|
|
5568
|
+
"--param",
|
|
5569
|
+
"params",
|
|
5570
|
+
multiple=True,
|
|
5571
|
+
help="Declared parameter name (repeatable). Substituted at run time.",
|
|
5572
|
+
)
|
|
5573
|
+
@click.option("--project-dir", type=click.Path(exists=True), default=".")
|
|
5574
|
+
def workflow_record(
|
|
5575
|
+
name: str,
|
|
5576
|
+
command: str,
|
|
5577
|
+
description: str,
|
|
5578
|
+
params: tuple[str, ...],
|
|
5579
|
+
project_dir: str,
|
|
5580
|
+
) -> None:
|
|
5581
|
+
"""Save a workflow under .specsmith/workflows/<NAME>.yml."""
|
|
5582
|
+
root = Path(project_dir).resolve()
|
|
5583
|
+
target = _workflows_dir(root) / f"{name}.yml"
|
|
5584
|
+
payload = {
|
|
5585
|
+
"name": name,
|
|
5586
|
+
"description": description,
|
|
5587
|
+
"command": command,
|
|
5588
|
+
"params": list(params),
|
|
5589
|
+
}
|
|
5590
|
+
target.write_text(yaml.safe_dump(payload, sort_keys=False), encoding="utf-8")
|
|
5591
|
+
console.print(f"[green]\u2713[/green] Workflow recorded at {target.relative_to(root)}")
|
|
5592
|
+
|
|
5593
|
+
|
|
5594
|
+
@workflow_group.command(name="list")
|
|
5595
|
+
@click.option("--project-dir", type=click.Path(exists=True), default=".")
|
|
5596
|
+
@click.option("--json", "as_json", is_flag=True, default=False, help="Emit JSON.")
|
|
5597
|
+
def workflow_list(project_dir: str, as_json: bool) -> None:
|
|
5598
|
+
"""List workflows recorded for this project."""
|
|
5599
|
+
import json as _json
|
|
5600
|
+
|
|
5601
|
+
root = Path(project_dir).resolve()
|
|
5602
|
+
wf_dir = _workflows_dir(root)
|
|
5603
|
+
items: list[dict[str, Any]] = []
|
|
5604
|
+
for path in sorted(wf_dir.glob("*.yml")):
|
|
5605
|
+
try:
|
|
5606
|
+
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
5607
|
+
except yaml.YAMLError:
|
|
5608
|
+
continue
|
|
5609
|
+
items.append(
|
|
5610
|
+
{
|
|
5611
|
+
"name": data.get("name", path.stem),
|
|
5612
|
+
"description": data.get("description", ""),
|
|
5613
|
+
"command": data.get("command", ""),
|
|
5614
|
+
"params": list(data.get("params", [])),
|
|
5615
|
+
}
|
|
5616
|
+
)
|
|
5617
|
+
if as_json:
|
|
5618
|
+
click.echo(_json.dumps(items, indent=2))
|
|
5619
|
+
return
|
|
5620
|
+
if not items:
|
|
5621
|
+
console.print("[dim]No workflows recorded.[/dim]")
|
|
5622
|
+
return
|
|
5623
|
+
for item in items:
|
|
5624
|
+
params = ", ".join(item["params"]) or "(none)"
|
|
5625
|
+
console.print(f"[bold]{item['name']}[/bold] — params: {params}")
|
|
5626
|
+
if item["description"]:
|
|
5627
|
+
console.print(f" {item['description']}")
|
|
5628
|
+
console.print(f" [dim]{item['command']}[/dim]")
|
|
5629
|
+
|
|
5630
|
+
|
|
5631
|
+
@workflow_group.command(name="run")
|
|
5632
|
+
@click.argument("name")
|
|
5633
|
+
@click.option(
|
|
5634
|
+
"--param",
|
|
5635
|
+
"param_assignments",
|
|
5636
|
+
multiple=True,
|
|
5637
|
+
help="Parameter assignment in key=value form (repeatable).",
|
|
5638
|
+
)
|
|
5639
|
+
@click.option(
|
|
5640
|
+
"--dry-run",
|
|
5641
|
+
is_flag=True,
|
|
5642
|
+
default=False,
|
|
5643
|
+
help="Print the resolved command without executing.",
|
|
5644
|
+
)
|
|
5645
|
+
@click.option("--project-dir", type=click.Path(exists=True), default=".")
|
|
5646
|
+
def workflow_run(
|
|
5647
|
+
name: str,
|
|
5648
|
+
param_assignments: tuple[str, ...],
|
|
5649
|
+
dry_run: bool,
|
|
5650
|
+
project_dir: str,
|
|
5651
|
+
) -> None:
|
|
5652
|
+
"""Substitute parameters and execute the recorded workflow."""
|
|
5653
|
+
import re
|
|
5654
|
+
import shlex
|
|
5655
|
+
import subprocess
|
|
5656
|
+
|
|
5657
|
+
root = Path(project_dir).resolve()
|
|
5658
|
+
target = _workflows_dir(root) / f"{name}.yml"
|
|
5659
|
+
if not target.is_file():
|
|
5660
|
+
console.print(f"[red]No workflow named '{name}' at {target}[/red]")
|
|
5661
|
+
raise SystemExit(1)
|
|
5662
|
+
data = yaml.safe_load(target.read_text(encoding="utf-8")) or {}
|
|
5663
|
+
template: str = data.get("command", "")
|
|
5664
|
+
declared = list(data.get("params", []))
|
|
5665
|
+
|
|
5666
|
+
assignments: dict[str, str] = {}
|
|
5667
|
+
for raw in param_assignments:
|
|
5668
|
+
if "=" not in raw:
|
|
5669
|
+
console.print(f"[red]Bad --param value: {raw!r} (expected key=value)[/red]")
|
|
5670
|
+
raise SystemExit(2)
|
|
5671
|
+
key, _, value = raw.partition("=")
|
|
5672
|
+
assignments[key.strip()] = value
|
|
5673
|
+
|
|
5674
|
+
missing = [p for p in declared if p not in assignments]
|
|
5675
|
+
if missing:
|
|
5676
|
+
console.print(f"[red]Missing required params: {', '.join(missing)}[/red]")
|
|
5677
|
+
raise SystemExit(2)
|
|
5678
|
+
|
|
5679
|
+
def _replace(match: re.Match[str]) -> str:
|
|
5680
|
+
key = match.group(1).strip()
|
|
5681
|
+
return assignments.get(key, match.group(0))
|
|
5682
|
+
|
|
5683
|
+
resolved = re.sub(r"\{\{\s*([^}]+?)\s*\}\}", _replace, template)
|
|
5684
|
+
|
|
5685
|
+
if dry_run:
|
|
5686
|
+
console.print(f"[cyan]{resolved}[/cyan]")
|
|
5687
|
+
return
|
|
5688
|
+
|
|
5689
|
+
args = shlex.split(resolved, posix=False) if resolved else []
|
|
5690
|
+
if not args:
|
|
5691
|
+
console.print("[red]Resolved workflow command is empty.[/red]")
|
|
5692
|
+
raise SystemExit(2)
|
|
5693
|
+
raise SystemExit(subprocess.call(args, cwd=str(root))) # noqa: S603
|
|
5694
|
+
|
|
5695
|
+
|
|
5696
|
+
main.add_command(workflow_group)
|
|
5697
|
+
|
|
5698
|
+
|
|
5699
|
+
# ---------------------------------------------------------------------------
|
|
5700
|
+
# History — search across .specsmith/sessions/<id>/turns.jsonl (REQ-120)
|
|
5701
|
+
# ---------------------------------------------------------------------------
|
|
5702
|
+
|
|
5703
|
+
|
|
5704
|
+
@main.group(name="history")
|
|
5705
|
+
def history_group() -> None:
|
|
5706
|
+
"""Search and list persistent session memory written by `specsmith chat`."""
|
|
5707
|
+
|
|
5708
|
+
|
|
5709
|
+
def _sessions_dir(root: Path) -> Path:
|
|
5710
|
+
return root / ".specsmith" / "sessions"
|
|
5711
|
+
|
|
5712
|
+
|
|
5713
|
+
@history_group.command(name="list")
|
|
5714
|
+
@click.option("--project-dir", type=click.Path(exists=True), default=".")
|
|
5715
|
+
@click.option("--limit", type=int, default=20, help="Max number of sessions to list.")
|
|
5716
|
+
@click.option("--json", "as_json", is_flag=True, default=False)
|
|
5717
|
+
def history_list(project_dir: str, limit: int, as_json: bool) -> None:
|
|
5718
|
+
"""List the N most recent sessions with turn counts."""
|
|
5719
|
+
import json as _json
|
|
5720
|
+
|
|
5721
|
+
root = Path(project_dir).resolve()
|
|
5722
|
+
base = _sessions_dir(root)
|
|
5723
|
+
if not base.is_dir():
|
|
5724
|
+
if as_json:
|
|
5725
|
+
click.echo("[]")
|
|
5726
|
+
else:
|
|
5727
|
+
console.print("[dim]No sessions recorded.[/dim]")
|
|
5728
|
+
return
|
|
5729
|
+
sessions = sorted(
|
|
5730
|
+
(p for p in base.iterdir() if p.is_dir()),
|
|
5731
|
+
key=lambda p: p.stat().st_mtime,
|
|
5732
|
+
reverse=True,
|
|
5733
|
+
)[:limit]
|
|
5734
|
+
items: list[dict[str, Any]] = []
|
|
5735
|
+
for sd in sessions:
|
|
5736
|
+
turns_path = sd / "turns.jsonl"
|
|
5737
|
+
count = 0
|
|
5738
|
+
if turns_path.is_file():
|
|
5739
|
+
with turns_path.open("r", encoding="utf-8") as fh:
|
|
5740
|
+
count = sum(1 for line in fh if line.strip())
|
|
5741
|
+
items.append({"session_id": sd.name, "turns": count, "path": str(turns_path)})
|
|
5742
|
+
if as_json:
|
|
5743
|
+
click.echo(_json.dumps(items, indent=2))
|
|
5744
|
+
return
|
|
5745
|
+
if not items:
|
|
5746
|
+
console.print("[dim]No sessions recorded.[/dim]")
|
|
5747
|
+
return
|
|
5748
|
+
for item in items:
|
|
5749
|
+
console.print(f"[bold]{item['session_id']}[/bold] {item['turns']} turn(s)")
|
|
5750
|
+
|
|
5751
|
+
|
|
5752
|
+
@history_group.command(name="search")
|
|
5753
|
+
@click.argument("query")
|
|
5754
|
+
@click.option("--project-dir", type=click.Path(exists=True), default=".")
|
|
5755
|
+
@click.option("--session", "session", default="", help="Limit to a specific session id.")
|
|
5756
|
+
@click.option("--limit", type=int, default=50, help="Max matching turns to print.")
|
|
5757
|
+
@click.option("--json", "as_json", is_flag=True, default=False)
|
|
5758
|
+
def history_search(
|
|
5759
|
+
query: str,
|
|
5760
|
+
project_dir: str,
|
|
5761
|
+
session: str,
|
|
5762
|
+
limit: int,
|
|
5763
|
+
as_json: bool,
|
|
5764
|
+
) -> None:
|
|
5765
|
+
"""Print turns whose JSON content contains QUERY (case-insensitive substring)."""
|
|
5766
|
+
import json as _json
|
|
5767
|
+
|
|
5768
|
+
root = Path(project_dir).resolve()
|
|
5769
|
+
base = _sessions_dir(root)
|
|
5770
|
+
if not base.is_dir():
|
|
5771
|
+
if as_json:
|
|
5772
|
+
click.echo("[]")
|
|
5773
|
+
return
|
|
5774
|
+
needle = query.lower()
|
|
5775
|
+
targets = [base / session / "turns.jsonl"] if session else sorted(base.rglob("turns.jsonl"))
|
|
5776
|
+
matches: list[dict[str, Any]] = []
|
|
5777
|
+
for path in targets:
|
|
5778
|
+
if not path.is_file():
|
|
5779
|
+
continue
|
|
5780
|
+
with path.open("r", encoding="utf-8") as fh:
|
|
5781
|
+
for raw in fh:
|
|
5782
|
+
if needle not in raw.lower():
|
|
5783
|
+
continue
|
|
5784
|
+
try:
|
|
5785
|
+
turn = _json.loads(raw)
|
|
5786
|
+
except _json.JSONDecodeError:
|
|
5787
|
+
continue
|
|
5788
|
+
matches.append({"session_id": path.parent.name, "turn": turn})
|
|
5789
|
+
if len(matches) >= limit:
|
|
5790
|
+
break
|
|
5791
|
+
if len(matches) >= limit:
|
|
5792
|
+
break
|
|
5793
|
+
if as_json:
|
|
5794
|
+
click.echo(_json.dumps(matches, indent=2))
|
|
5795
|
+
return
|
|
5796
|
+
if not matches:
|
|
5797
|
+
console.print("[dim]No matches.[/dim]")
|
|
5798
|
+
return
|
|
5799
|
+
for hit in matches:
|
|
5800
|
+
console.print(f"[bold]{hit['session_id']}[/bold]: {_json.dumps(hit['turn'])[:200]}")
|
|
5801
|
+
|
|
5802
|
+
|
|
5803
|
+
main.add_command(history_group)
|
|
5804
|
+
|
|
5805
|
+
|
|
5806
|
+
# ---------------------------------------------------------------------------
|
|
5807
|
+
# Drive — user-scoped sync for rules / workflows / notebooks / mcp configs
|
|
5808
|
+
# ---------------------------------------------------------------------------
|
|
5809
|
+
|
|
5810
|
+
_DRIVE_KINDS = {
|
|
5811
|
+
"rules": ("docs/governance",),
|
|
5812
|
+
"workflows": (".specsmith/workflows",),
|
|
5813
|
+
"notebooks": ("docs/notebooks",),
|
|
5814
|
+
"mcp": (".specsmith/mcp.yml",),
|
|
5815
|
+
}
|
|
5816
|
+
|
|
5817
|
+
|
|
5818
|
+
def _drive_root() -> Path:
|
|
5819
|
+
home = Path.home()
|
|
5820
|
+
base = home / ".specsmith" / "drive"
|
|
5821
|
+
base.mkdir(parents=True, exist_ok=True)
|
|
5822
|
+
return base
|
|
5823
|
+
|
|
5824
|
+
|
|
5825
|
+
@main.group(name="drive")
|
|
5826
|
+
def drive_group() -> None:
|
|
5827
|
+
"""User-scoped Drive at ~/.specsmith/drive/ for rules / workflows / notebooks.
|
|
5828
|
+
|
|
5829
|
+
The Drive is a local, gitignored mirror of the four kinds of project
|
|
5830
|
+
artefacts that users typically want to share across machines:
|
|
5831
|
+
``rules`` (docs/governance/*_RULES.md), ``workflows``
|
|
5832
|
+
(.specsmith/workflows/*.yml), ``notebooks`` (docs/notebooks/*.md), and
|
|
5833
|
+
``mcp`` (.specsmith/mcp.yml). Cloud sync is left to the user's preferred
|
|
5834
|
+
backup tool — Drive is a stable canonical location, not a server.
|
|
5835
|
+
"""
|
|
5836
|
+
|
|
5837
|
+
|
|
5838
|
+
@drive_group.command(name="list")
|
|
5839
|
+
@click.option("--json", "as_json", is_flag=True, default=False)
|
|
5840
|
+
def drive_list(as_json: bool) -> None:
|
|
5841
|
+
"""Show the contents of ~/.specsmith/drive/ grouped by kind."""
|
|
5842
|
+
import json as _json
|
|
5843
|
+
|
|
5844
|
+
base = _drive_root()
|
|
5845
|
+
items: dict[str, list[str]] = {}
|
|
5846
|
+
for kind in _DRIVE_KINDS:
|
|
5847
|
+
kind_dir = base / kind
|
|
5848
|
+
if not kind_dir.is_dir():
|
|
5849
|
+
items[kind] = []
|
|
5850
|
+
continue
|
|
5851
|
+
items[kind] = sorted(
|
|
5852
|
+
str(p.relative_to(kind_dir)) for p in kind_dir.rglob("*") if p.is_file()
|
|
5853
|
+
)
|
|
5854
|
+
if as_json:
|
|
5855
|
+
click.echo(_json.dumps(items, indent=2))
|
|
5856
|
+
return
|
|
5857
|
+
for kind, paths in items.items():
|
|
5858
|
+
console.print(f"[bold]{kind}[/bold] ({len(paths)} item(s))")
|
|
5859
|
+
for rel in paths:
|
|
5860
|
+
console.print(f" {rel}")
|
|
5861
|
+
|
|
5862
|
+
|
|
5863
|
+
@drive_group.command(name="push")
|
|
5864
|
+
@click.argument("kind", type=click.Choice(sorted(_DRIVE_KINDS.keys())))
|
|
5865
|
+
@click.option("--project-dir", type=click.Path(exists=True), default=".")
|
|
5866
|
+
def drive_push(kind: str, project_dir: str) -> None:
|
|
5867
|
+
"""Copy this project's KIND artefacts into ~/.specsmith/drive/KIND/."""
|
|
5868
|
+
import shutil
|
|
5869
|
+
|
|
5870
|
+
root = Path(project_dir).resolve()
|
|
5871
|
+
base = _drive_root() / kind
|
|
5872
|
+
base.mkdir(parents=True, exist_ok=True)
|
|
5873
|
+
sources = _DRIVE_KINDS[kind]
|
|
5874
|
+
copied = 0
|
|
5875
|
+
for rel in sources:
|
|
5876
|
+
src = root / rel
|
|
5877
|
+
if not src.exists():
|
|
5878
|
+
continue
|
|
5879
|
+
if src.is_file():
|
|
5880
|
+
shutil.copy2(src, base / src.name)
|
|
5881
|
+
copied += 1
|
|
5882
|
+
continue
|
|
5883
|
+
for path in src.rglob("*"):
|
|
5884
|
+
if not path.is_file():
|
|
5885
|
+
continue
|
|
5886
|
+
target = base / path.relative_to(src)
|
|
5887
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
5888
|
+
shutil.copy2(path, target)
|
|
5889
|
+
copied += 1
|
|
5890
|
+
console.print(f"[green]\u2713[/green] Pushed {copied} file(s) to {base}")
|
|
5891
|
+
|
|
5892
|
+
|
|
5893
|
+
@drive_group.command(name="pull")
|
|
5894
|
+
@click.argument("kind", type=click.Choice(sorted(_DRIVE_KINDS.keys())))
|
|
5895
|
+
@click.option("--project-dir", type=click.Path(exists=True), default=".")
|
|
5896
|
+
@click.option("--force", is_flag=True, default=False, help="Overwrite existing project files.")
|
|
5897
|
+
def drive_pull(kind: str, project_dir: str, force: bool) -> None:
|
|
5898
|
+
"""Copy KIND artefacts from ~/.specsmith/drive/ into this project.
|
|
5899
|
+
|
|
5900
|
+
Existing project files are preserved unless --force is supplied.
|
|
5901
|
+
"""
|
|
5902
|
+
import shutil
|
|
5903
|
+
|
|
5904
|
+
root = Path(project_dir).resolve()
|
|
5905
|
+
base = _drive_root() / kind
|
|
5906
|
+
if not base.is_dir():
|
|
5907
|
+
console.print(f"[yellow]Drive has no {kind!r} entries yet.[/yellow]")
|
|
5908
|
+
return
|
|
5909
|
+
target_root = root / _DRIVE_KINDS[kind][0]
|
|
5910
|
+
pulled = skipped = 0
|
|
5911
|
+
if base.is_dir() and target_root.suffix == "":
|
|
5912
|
+
target_root.mkdir(parents=True, exist_ok=True)
|
|
5913
|
+
for path in base.rglob("*"):
|
|
5914
|
+
if not path.is_file():
|
|
5915
|
+
continue
|
|
5916
|
+
dest = target_root / path.relative_to(base)
|
|
5917
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
5918
|
+
if dest.exists() and not force:
|
|
5919
|
+
skipped += 1
|
|
5920
|
+
continue
|
|
5921
|
+
shutil.copy2(path, dest)
|
|
5922
|
+
pulled += 1
|
|
5923
|
+
else:
|
|
5924
|
+
# Single-file kind (e.g. mcp.yml).
|
|
5925
|
+
for path in base.iterdir():
|
|
5926
|
+
if not path.is_file():
|
|
5927
|
+
continue
|
|
5928
|
+
dest = root / _DRIVE_KINDS[kind][0]
|
|
5929
|
+
if dest.exists() and not force:
|
|
5930
|
+
skipped += 1
|
|
5931
|
+
continue
|
|
5932
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
5933
|
+
shutil.copy2(path, dest)
|
|
5934
|
+
pulled += 1
|
|
5935
|
+
console.print(
|
|
5936
|
+
f"[green]\u2713[/green] Pulled {pulled} file(s) into {target_root}; "
|
|
5937
|
+
f"skipped {skipped} (use --force to overwrite)."
|
|
5938
|
+
)
|
|
5939
|
+
|
|
5940
|
+
|
|
5941
|
+
main.add_command(drive_group)
|
|
5942
|
+
|
|
5943
|
+
|
|
5535
5944
|
# ---------------------------------------------------------------------------
|
|
5536
5945
|
# AG2 Agent Shell
|
|
5537
5946
|
# ---------------------------------------------------------------------------
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: specsmith
|
|
3
|
-
Version: 0.5.0.
|
|
3
|
+
Version: 0.5.0.dev225
|
|
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
|