specify-cli 0.12.11__tar.gz → 0.12.12__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.
- {specify_cli-0.12.11 → specify_cli-0.12.12}/CHANGELOG.md +15 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/PKG-INFO +1 -1
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/community/extensions.md +1 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/reference/bundles.md +5 -4
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/reference/integrations.md +2 -1
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/catalog.community.json +35 -1
- {specify_cli-0.12.11 → specify_cli-0.12.12}/pyproject.toml +1 -1
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/commands/bundle/__init__.py +37 -23
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/extensions/__init__.py +20 -6
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/extensions/_commands.py +8 -1
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/presets/_commands.py +8 -1
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/workflows/catalog.py +13 -5
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/workflows/engine.py +10 -1
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/contract/test_bundle_cli.py +29 -7
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integration/test_bundler_local_install.py +59 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_extensions.py +89 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_presets.py +34 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_workflows.py +97 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.devcontainer/devcontainer.json +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.devcontainer/post-create.sh +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.editorconfig +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.gitattributes +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/CODEOWNERS +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/ISSUE_TEMPLATE/agent_request.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/ISSUE_TEMPLATE/bundle_submission.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/ISSUE_TEMPLATE/extension_submission.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/ISSUE_TEMPLATE/preset_submission.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/aw/actions-lock.json +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/dependabot.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/skills/add-community-extension/SKILL.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/workflows/RELEASE-PROCESS.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/workflows/add-community-extension.lock.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/workflows/add-community-extension.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/workflows/add-community-preset.lock.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/workflows/add-community-preset.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/workflows/bug-assess.lock.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/workflows/bug-assess.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/workflows/bug-fix.lock.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/workflows/bug-fix.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/workflows/bug-test.lock.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/workflows/bug-test.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/workflows/catalog-assign.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/workflows/codeql.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/workflows/docs.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/workflows/lint.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/workflows/publish-pypi.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/workflows/release-trigger.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/workflows/release.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/workflows/stale.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.github/workflows/test.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.gitignore +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.markdownlint-cli2.jsonc +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.pre-commit-config.yaml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/.zenodo.json +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/AGENTS.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/CITATION.cff +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/CODE_OF_CONDUCT.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/CONTRIBUTING.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/DEVELOPMENT.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/LICENSE +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/README.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/SECURITY.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/SUPPORT.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/.gitignore +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/README.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/community/bundles.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/community/friends.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/community/overview.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/community/presets.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/community/walkthroughs.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/concepts/complex-features.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/concepts/sdd.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/concepts/spec-persistence.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/docfx.json +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/guides/evolving-specs.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/guides/monorepo.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/index.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/install/air-gapped.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/install/one-time.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/install/pipx.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/install/uv.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/installation.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/local-development.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/quickstart.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/reference/authentication.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/reference/core.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/reference/extensions.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/reference/overview.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/reference/presets.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/reference/workflows.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/template/public/main.css +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/toc.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/docs/upgrade.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/examples/bundles/business-analyst/README.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/examples/bundles/business-analyst/bundle.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/examples/bundles/developer/README.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/examples/bundles/developer/bundle.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/examples/bundles/product-manager/README.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/examples/bundles/product-manager/bundle.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/examples/bundles/security-researcher/README.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/examples/bundles/security-researcher/bundle.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/EXTENSION-API-REFERENCE.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/EXTENSION-DEVELOPMENT-GUIDE.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/EXTENSION-PUBLISHING-GUIDE.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/EXTENSION-USER-GUIDE.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/README.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/RFC-EXTENSION-SYSTEM.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/agent-context/README.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/agent-context/agent-context-config.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/agent-context/agent-context-defaults.json +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/agent-context/commands/speckit.agent-context.update.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/agent-context/extension.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/agent-context/scripts/bash/update-agent-context.sh +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/agent-context/scripts/powershell/update-agent-context.ps1 +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/agent-context/scripts/python/update_agent_context.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/bug/README.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/bug/commands/speckit.bug.assess.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/bug/commands/speckit.bug.fix.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/bug/commands/speckit.bug.test.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/bug/extension.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/catalog.json +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/git/README.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/git/commands/speckit.git.commit.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/git/commands/speckit.git.feature.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/git/commands/speckit.git.initialize.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/git/commands/speckit.git.remote.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/git/commands/speckit.git.validate.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/git/config-template.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/git/extension.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/git/git-config.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/git/scripts/bash/auto-commit.sh +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/git/scripts/bash/create-new-feature-branch.sh +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/git/scripts/bash/git-common.sh +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/git/scripts/bash/initialize-repo.sh +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/git/scripts/powershell/auto-commit.ps1 +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/git/scripts/powershell/create-new-feature-branch.ps1 +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/git/scripts/powershell/git-common.ps1 +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/git/scripts/powershell/initialize-repo.ps1 +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/selftest/commands/selftest.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/selftest/extension.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/template/.gitignore +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/template/CHANGELOG.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/template/EXAMPLE-README.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/template/LICENSE +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/template/README.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/template/commands/example.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/template/config-template.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/extensions/template/extension.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/integrations/CONTRIBUTING.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/integrations/README.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/integrations/catalog.community.json +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/integrations/catalog.json +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/media/bootstrap-claude-code.gif +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/media/logo_large.webp +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/media/logo_small.webp +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/media/spec-kit-video-header.jpg +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/media/specify_cli.gif +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/newsletters/2026-April.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/newsletters/2026-February.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/newsletters/2026-June.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/newsletters/2026-March.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/newsletters/2026-May.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/ARCHITECTURE.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/PUBLISHING.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/README.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/catalog.community.json +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/catalog.json +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/lean/README.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/lean/commands/speckit.constitution.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/lean/commands/speckit.implement.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/lean/commands/speckit.plan.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/lean/commands/speckit.specify.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/lean/commands/speckit.tasks.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/lean/preset.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/scaffold/README.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/scaffold/commands/speckit.myext.myextcmd.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/scaffold/commands/speckit.specify.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/scaffold/preset.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/scaffold/templates/myext-template.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/scaffold/templates/spec-template.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/self-test/commands/speckit.specify.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/self-test/commands/speckit.wrap-test.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/self-test/preset.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/self-test/templates/agent-file-template.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/self-test/templates/checklist-template.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/self-test/templates/constitution-template.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/self-test/templates/plan-template.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/self-test/templates/spec-template.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/presets/self-test/templates/tasks-template.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/scripts/bash/check-prerequisites.sh +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/scripts/bash/common.sh +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/scripts/bash/create-new-feature.sh +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/scripts/bash/setup-plan.sh +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/scripts/bash/setup-tasks.sh +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/scripts/powershell/check-prerequisites.ps1 +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/scripts/powershell/common.ps1 +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/scripts/powershell/create-new-feature.ps1 +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/scripts/powershell/setup-plan.ps1 +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/scripts/powershell/setup-tasks.ps1 +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/scripts/python/check_prerequisites.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/scripts/python/common.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/spec-driven.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/spec-kit.code-workspace +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/_agent_config.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/_assets.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/_console.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/_github_http.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/_init_options.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/_invocation_style.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/_project.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/_toml_string.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/_utils.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/_version.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/agents.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/authentication/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/authentication/azure_devops.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/authentication/base.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/authentication/config.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/authentication/github.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/authentication/http.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/bundler/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/bundler/commands_impl/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/bundler/commands_impl/catalog_config.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/bundler/lib/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/bundler/lib/project.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/bundler/lib/versioning.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/bundler/lib/yamlio.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/bundler/models/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/bundler/models/catalog.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/bundler/models/manifest.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/bundler/models/records.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/bundler/services/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/bundler/services/adapters.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/bundler/services/catalog_stack.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/bundler/services/conflict.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/bundler/services/installer.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/bundler/services/packager.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/bundler/services/primitives.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/bundler/services/references.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/bundler/services/resolver.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/bundler/services/validator.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/catalogs.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/commands/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/commands/init.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integration_runtime.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integration_scaffold.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integration_state.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integration_status.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/_commands.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/_helpers.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/_install_commands.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/_migrate_commands.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/_query_commands.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/_scaffold_commands.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/agy/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/amp/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/auggie/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/base.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/bob/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/catalog.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/claude/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/cline/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/codebuddy/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/codex/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/copilot/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/cursor_agent/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/devin/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/firebender/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/forge/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/gemini/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/generic/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/goose/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/hermes/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/junie/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/kilocode/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/kimi/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/kiro_cli/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/lingma/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/manifest.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/omp/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/opencode/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/pi/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/qodercli/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/qwen/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/rovodev/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/shai/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/tabnine/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/trae/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/vibe/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/zcode/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/integrations/zed/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/presets/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/shared_infra.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/workflows/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/workflows/_commands.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/workflows/base.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/workflows/expressions.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/workflows/steps/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/workflows/steps/command/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/workflows/steps/do_while/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/workflows/steps/fan_in/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/workflows/steps/fan_out/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/workflows/steps/gate/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/workflows/steps/if_then/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/workflows/steps/init/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/workflows/steps/prompt/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/workflows/steps/shell/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/workflows/steps/switch/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/src/specify_cli/workflows/steps/while_loop/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/templates/checklist-template.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/templates/commands/analyze.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/templates/commands/checklist.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/templates/commands/clarify.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/templates/commands/constitution.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/templates/commands/converge.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/templates/commands/implement.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/templates/commands/plan.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/templates/commands/specify.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/templates/commands/tasks.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/templates/commands/taskstoissues.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/templates/constitution-template.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/templates/plan-template.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/templates/spec-template.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/templates/tasks-template.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/templates/vscode-settings.json +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/auth_helpers.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/bundler_helpers.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/conftest.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/contract/test_catalog_schema.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/contract/test_manifest_schema.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/extensions/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/extensions/bug/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/extensions/bug/test_bug_extension.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/extensions/git/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/extensions/git/test_git_extension.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/extensions/test_agent_context_cli_free.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/extensions/test_extension_agent_context.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/extensions/test_update_agent_context_feature_json.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/extensions/test_update_agent_context_python_parity.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/hooks/TESTING.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/hooks/plan.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/hooks/spec.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/hooks/tasks.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/http_helpers.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integration/test_bundler_catalog_stack.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integration/test_bundler_init_install.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integration/test_bundler_install_flow.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integration/test_bundler_offline.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integration/test_bundler_security_paths.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/__init__.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/conftest.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_base.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_cli.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_extra_args.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_home_isolation.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_agy.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_amp.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_auggie.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_base_markdown.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_base_skills.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_base_toml.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_base_yaml.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_bob.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_catalog.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_claude.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_cline.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_codebuddy.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_codex.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_copilot.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_cursor_agent.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_devin.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_firebender.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_forge.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_gemini.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_generic.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_goose.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_hermes.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_junie.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_kilocode.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_kimi.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_kiro_cli.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_lingma.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_omp.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_opencode.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_pi.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_qodercli.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_qwen.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_rovodev.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_scaffold.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_shai.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_state.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_subcommand.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_tabnine.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_trae.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_vibe.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_zcode.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_integration_zed.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_manifest.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_registry.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/integrations/test_skill_frontmatter_quoting.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/self_upgrade_helpers.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_agent_config_consistency.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_authentication.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_branch_numbering.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_check_prerequisites_paths_only.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_check_prerequisites_python_parity.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_check_tool.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_cli_version.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_command_template_py_scripts.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_commands_package.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_console_imports.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_extension_registration.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_extension_skills.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_extension_update_hardening.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_github_http.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_github_workflows.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_init_dir.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_init_dir_cli.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_live_transient_windows.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_merge.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_post_process.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_ps1_encoding.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_registrar_path_traversal.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_self_upgrade_detection.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_self_upgrade_execution.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_self_upgrade_guidance.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_self_upgrade_verification.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_setup_plan_feature_json.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_setup_plan_no_overwrite.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_setup_tasks.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_shared_infra_integrity.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_timestamp_branches.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_upgrade.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_utils.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_utils_assets_imports.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_version_imports.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/test_workflow_run_without_project.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/unit/test_bundler_adapters.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/unit/test_bundler_catalog_config.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/unit/test_bundler_conflict.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/unit/test_bundler_packager.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/unit/test_bundler_primitives.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/unit/test_bundler_records.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/unit/test_bundler_references.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/unit/test_bundler_resolver.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/unit/test_bundler_validator.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/tests/unit/test_bundler_versioning.py +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/workflows/ARCHITECTURE.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/workflows/PUBLISHING.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/workflows/README.md +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/workflows/catalog.community.json +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/workflows/catalog.json +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/workflows/speckit/workflow.yml +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/workflows/step-catalog.community.json +0 -0
- {specify_cli-0.12.11 → specify_cli-0.12.12}/workflows/step-catalog.json +0 -0
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- insert new changelog below this comment -->
|
|
4
4
|
|
|
5
|
+
## [0.12.12] - 2026-07-13
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- fix(extensions): set-priority repairs corrupted boolean priority (#3268)
|
|
10
|
+
- fix(presets): set-priority repairs corrupted boolean priority (#3269)
|
|
11
|
+
- fix(workflows): engine loop cap ignores bool max_iterations (#3270)
|
|
12
|
+
- docs(bundles): document --integration on 'bundle update' (#3271)
|
|
13
|
+
- fix(workflows): harden catalog.py against mis-shaped registry & non-string fields (#3375)
|
|
14
|
+
- Add Verify Review Ship extension to community catalog (#3450)
|
|
15
|
+
- fix(bundle): reject file:// / local download_url — catalog URLs are HTTPS-only (#3344)
|
|
16
|
+
- fix(extensions): handle prefix-colliding env vars in _get_env_config (#3350)
|
|
17
|
+
- docs: document copilot skills mode (--skills) and markdown deprecation (#3313)
|
|
18
|
+
- chore: release 0.12.11, begin 0.12.12.dev0 development (#3460)
|
|
19
|
+
|
|
5
20
|
## [0.12.11] - 2026-07-10
|
|
6
21
|
|
|
7
22
|
### Changed
|
|
@@ -148,6 +148,7 @@ The following community-contributed extensions are available in [`catalog.commun
|
|
|
148
148
|
| Token Economy | Token routing, measured savings, and context audit workflows | `process` | Read+Write | [spec-kit-token-economy](https://github.com/formin/spec-kit-token-economy) |
|
|
149
149
|
| V-Model Extension Pack | Enforces V-Model paired generation of development specs and test specs with full traceability | `docs` | Read+Write | [spec-kit-v-model](https://github.com/leocamello/spec-kit-v-model) |
|
|
150
150
|
| Verify Extension | Post-implementation quality gate that validates implemented code against specification artifacts | `code` | Read-only | [spec-kit-verify](https://github.com/ismaelJimenez/spec-kit-verify) |
|
|
151
|
+
| Verify Review Ship | Adds post-implementation verify, review, and ship readiness gates to Spec Kit workflows | `process` | Read-only | [spec-kit-verify-review-ship](https://github.com/cadugevaerd/spec-kit-verify-review-ship) |
|
|
151
152
|
| Verify Tasks Extension | Detect phantom completions: tasks marked [X] in tasks.md with no real implementation | `code` | Read-only | [spec-kit-verify-tasks](https://github.com/datastone-inc/spec-kit-verify-tasks) |
|
|
152
153
|
| Version Guard | Verify tech stack versions against live npm registries before planning and implementation | `process` | Read-only | [spec-kit-version-guard](https://github.com/KevinBrown5280/spec-kit-version-guard) |
|
|
153
154
|
| What-if Analysis | Preview the downstream impact (complexity, effort, tasks, risks) of requirement changes before committing to them | `visibility` | Read-only | [spec-kit-whatif](https://github.com/DevAbdullah90/spec-kit-whatif) |
|
|
@@ -51,10 +51,11 @@ If the current directory is not yet a Spec Kit project, `install` initializes on
|
|
|
51
51
|
specify bundle update [<bundle_id>]
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
| Option
|
|
55
|
-
|
|
|
56
|
-
| `--all`
|
|
57
|
-
| `--
|
|
54
|
+
| Option | Description |
|
|
55
|
+
| ---------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
56
|
+
| `--all` | Update every installed bundle |
|
|
57
|
+
| `--integration` | Override the integration used when refreshing components; applied only when the project's active integration can't be determined |
|
|
58
|
+
| `--offline` | Do not access the network |
|
|
58
59
|
|
|
59
60
|
Re-resolves a bundle and **refreshes** its components through each primitive's update path, bringing already-installed components up to the bundle's newly pinned versions while preserving primitive-level overrides (such as preset priority). Provide a bundle id, or use `--all` to update everything installed.
|
|
60
61
|
|
|
@@ -18,7 +18,7 @@ The Specify CLI supports a wide range of AI coding agents. When you run `specify
|
|
|
18
18
|
| [Firebender](https://firebender.com/) | `firebender` | IDE-based agent for Android Studio / IntelliJ |
|
|
19
19
|
| [Forge](https://forgecode.dev/) | `forge` | |
|
|
20
20
|
| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | `gemini` | |
|
|
21
|
-
| [GitHub Copilot](https://code.visualstudio.com/) | `copilot` |
|
|
21
|
+
| [GitHub Copilot](https://code.visualstudio.com/) | `copilot` | Defaults to legacy markdown mode: `.agent.md` command files under `.github/agents/`, companion `.prompt.md` files under `.github/prompts/`, and a `.vscode/settings.json` merge. Pass `--integration-options="--skills"` to scaffold skills as `speckit-<command>/SKILL.md` under `.github/skills/` instead. Legacy markdown mode is deprecated and will stop being the default in a future release. |
|
|
22
22
|
| [Goose](https://goose-docs.ai/) | `goose` | Uses YAML recipe format in `.goose/recipes/` |
|
|
23
23
|
| [Hermes](https://github.com/NousResearch/hermes-agent) | `hermes` | Skills-based integration; installs skills globally into `~/.hermes/skills/` |
|
|
24
24
|
| [IBM Bob](https://www.ibm.com/products/bob) | `bob` | IDE-based agent |
|
|
@@ -219,6 +219,7 @@ Some integrations accept additional options via `--integration-options`:
|
|
|
219
219
|
| ----------- | ------------------- | -------------------------------------------------------------- |
|
|
220
220
|
| `generic` | `--commands-dir` | Required. Directory for command files |
|
|
221
221
|
| `kimi` | `--migrate-legacy` | Migrate legacy `.kimi/skills/` installs to `.kimi-code/skills/` (including dotted→hyphenated skill naming, e.g. `speckit.xxx` → `speckit-xxx`) |
|
|
222
|
+
| `copilot` | `--skills` | Scaffold commands as agent skills (`speckit-<command>/SKILL.md` under `.github/skills/`, invoked as `/speckit-<command>`) instead of the default legacy markdown mode (`.github/agents/*.agent.md` plus `.github/prompts/*.prompt.md` and a `.vscode/settings.json` merge). Without this flag, install warns that legacy markdown mode is deprecated. |
|
|
222
223
|
|
|
223
224
|
Example:
|
|
224
225
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": "1.0",
|
|
3
|
-
"updated_at": "2026-07-
|
|
3
|
+
"updated_at": "2026-07-10T00:00:00Z",
|
|
4
4
|
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json",
|
|
5
5
|
"extensions": {
|
|
6
6
|
"aide": {
|
|
@@ -4356,6 +4356,40 @@
|
|
|
4356
4356
|
"created_at": "2026-03-03T00:00:00Z",
|
|
4357
4357
|
"updated_at": "2026-04-09T00:00:00Z"
|
|
4358
4358
|
},
|
|
4359
|
+
"verify-review-ship": {
|
|
4360
|
+
"name": "Verify Review Ship",
|
|
4361
|
+
"id": "verify-review-ship",
|
|
4362
|
+
"description": "Adds post-implementation verify, review, and ship readiness gates to Spec Kit workflows.",
|
|
4363
|
+
"author": "Carlos Eduardo Gevaerd Araujo",
|
|
4364
|
+
"version": "0.1.0",
|
|
4365
|
+
"download_url": "https://github.com/cadugevaerd/spec-kit-verify-review-ship/archive/refs/tags/v0.1.0.zip",
|
|
4366
|
+
"repository": "https://github.com/cadugevaerd/spec-kit-verify-review-ship",
|
|
4367
|
+
"homepage": "https://github.com/cadugevaerd/spec-kit-verify-review-ship",
|
|
4368
|
+
"documentation": "https://github.com/cadugevaerd/spec-kit-verify-review-ship/blob/main/README.md",
|
|
4369
|
+
"changelog": "https://github.com/cadugevaerd/spec-kit-verify-review-ship/blob/main/CHANGELOG.md",
|
|
4370
|
+
"license": "MIT",
|
|
4371
|
+
"category": "process",
|
|
4372
|
+
"effect": "read-only",
|
|
4373
|
+
"requires": {
|
|
4374
|
+
"speckit_version": ">=0.1.0"
|
|
4375
|
+
},
|
|
4376
|
+
"provides": {
|
|
4377
|
+
"commands": 3,
|
|
4378
|
+
"hooks": 1
|
|
4379
|
+
},
|
|
4380
|
+
"tags": [
|
|
4381
|
+
"quality",
|
|
4382
|
+
"review",
|
|
4383
|
+
"shipping",
|
|
4384
|
+
"workflow",
|
|
4385
|
+
"testing"
|
|
4386
|
+
],
|
|
4387
|
+
"verified": false,
|
|
4388
|
+
"downloads": 0,
|
|
4389
|
+
"stars": 0,
|
|
4390
|
+
"created_at": "2026-07-10T00:00:00Z",
|
|
4391
|
+
"updated_at": "2026-07-10T00:00:00Z"
|
|
4392
|
+
},
|
|
4359
4393
|
"verify-tasks": {
|
|
4360
4394
|
"name": "Verify Tasks Extension",
|
|
4361
4395
|
"id": "verify-tasks",
|
|
@@ -746,11 +746,16 @@ def _resolve_manifest_path(path: Path | None) -> Path:
|
|
|
746
746
|
def _download_manifest(resolved, *, offline: bool):
|
|
747
747
|
"""Resolve a bundle's manifest from its catalog ``download_url``.
|
|
748
748
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
749
|
+
Catalog ``download_url``s are HTTPS-only (``http`` allowed for localhost),
|
|
750
|
+
matching the extensions/presets/workflows catalog systems. Remote URLs are
|
|
751
|
+
fetched with the shared authenticated, redirect-validated HTTP client, and
|
|
752
|
+
only when not ``--offline``.
|
|
753
|
+
|
|
754
|
+
Local and ``file://`` sources are intentionally not resolved here: to
|
|
755
|
+
install a bundle from disk, pass the path positionally
|
|
756
|
+
(``specify bundle install ./path/to/bundle.yml`` — a bundle directory or a
|
|
757
|
+
``.zip`` artifact also works), which :func:`_local_manifest_source` handles
|
|
758
|
+
before catalog resolution and which never touches ``download_url``.
|
|
754
759
|
"""
|
|
755
760
|
from urllib.parse import urlparse
|
|
756
761
|
|
|
@@ -763,26 +768,35 @@ def _download_manifest(resolved, *, offline: bool):
|
|
|
763
768
|
parsed = urlparse(url)
|
|
764
769
|
scheme = parsed.scheme.lower()
|
|
765
770
|
|
|
766
|
-
#
|
|
767
|
-
#
|
|
771
|
+
# ``file://`` URLs and bare filesystem paths (including Windows drive paths
|
|
772
|
+
# like ``C:\bundle.yml``, which urlparse reads as a single-letter scheme)
|
|
773
|
+
# are not valid catalog download URLs. Catalog URLs are HTTPS-only across
|
|
774
|
+
# every catalog system; installing from disk is done by passing the path
|
|
775
|
+
# positionally, which never reaches URL resolution. Give an actionable
|
|
776
|
+
# error rather than accepting a scheme the rest of the codebase rejects.
|
|
768
777
|
if scheme in ("", "file") or re.match(r"^[A-Za-z]:[\\/]", url):
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
raise BundlerError(
|
|
778
|
-
f"Network access disabled; cannot download bundle '{resolved.entry.id}' "
|
|
779
|
-
f"from {url}."
|
|
780
|
-
)
|
|
781
|
-
return _download_remote_manifest(resolved.entry.id, url)
|
|
778
|
+
raise BundlerError(
|
|
779
|
+
f"Catalog entry '{resolved.entry.id}' has a non-HTTP(S) download_url "
|
|
780
|
+
f"({url}); catalog download URLs must be HTTPS (http for localhost) — "
|
|
781
|
+
"a file:// URL, a local filesystem path, or a scheme-less value "
|
|
782
|
+
"(e.g. 'example.com/bundle.zip') is not accepted. "
|
|
783
|
+
"To install a bundle from disk, pass the path directly: "
|
|
784
|
+
"'specify bundle install <path-to-bundle.yml | bundle-dir | .zip>'."
|
|
785
|
+
)
|
|
782
786
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
787
|
+
# Validate the scheme/host *before* the offline gate so an invalid or
|
|
788
|
+
# non-HTTPS download_url reports the real problem in every mode, rather
|
|
789
|
+
# than a misleading "Network access disabled" under --offline.
|
|
790
|
+
# (_download_remote_manifest re-checks this, but only once network access
|
|
791
|
+
# is permitted.) HTTPS-only, http allowed for localhost.
|
|
792
|
+
_require_https(f"bundle '{resolved.entry.id}'", url)
|
|
793
|
+
|
|
794
|
+
if offline:
|
|
795
|
+
raise BundlerError(
|
|
796
|
+
f"Network access disabled; cannot download bundle '{resolved.entry.id}' "
|
|
797
|
+
f"from {url}."
|
|
798
|
+
)
|
|
799
|
+
return _download_remote_manifest(resolved.entry.id, url)
|
|
786
800
|
|
|
787
801
|
|
|
788
802
|
def _require_https(label: str, url: str) -> None:
|
|
@@ -2755,18 +2755,32 @@ class ConfigManager:
|
|
|
2755
2755
|
if not key.startswith(prefix):
|
|
2756
2756
|
continue
|
|
2757
2757
|
|
|
2758
|
-
# Remove prefix and split into parts
|
|
2759
|
-
|
|
2758
|
+
# Remove prefix and split into parts. Drop empty components from a
|
|
2759
|
+
# malformed name (e.g. ``SPECKIT_<EXT>_`` with no key, or
|
|
2760
|
+
# consecutive underscores ``SPECKIT_X__Y``) so we never create an
|
|
2761
|
+
# entry under an empty key.
|
|
2762
|
+
config_path = [p for p in key[len(prefix) :].lower().split("_") if p]
|
|
2763
|
+
if not config_path:
|
|
2764
|
+
continue
|
|
2760
2765
|
|
|
2761
|
-
# Build nested dict
|
|
2766
|
+
# Build nested dict. Two env vars can collide on a prefix, e.g.
|
|
2767
|
+
# SPECKIT_X_CONNECTION=a and SPECKIT_X_CONNECTION_URL=b. Guard the
|
|
2768
|
+
# walk so a colliding scalar is replaced by a dict (deeper/more
|
|
2769
|
+
# specific vars win) instead of being indexed into — which raised
|
|
2770
|
+
# TypeError ('str' object does not support item assignment) — and
|
|
2771
|
+
# guard the leaf so a scalar processed after the nested var does
|
|
2772
|
+
# not clobber the nested dict. Order-independent: both insertion
|
|
2773
|
+
# orders yield {'connection': {'url': ...}}. Nested-wins mirrors
|
|
2774
|
+
# _merge_configs' dict-preserving semantics.
|
|
2762
2775
|
current = env_config
|
|
2763
2776
|
for part in config_path[:-1]:
|
|
2764
|
-
if
|
|
2777
|
+
if not isinstance(current.get(part), dict):
|
|
2765
2778
|
current[part] = {}
|
|
2766
2779
|
current = current[part]
|
|
2767
2780
|
|
|
2768
|
-
# Set the final value
|
|
2769
|
-
current
|
|
2781
|
+
# Set the final value, unless a nested dict already occupies it.
|
|
2782
|
+
if not isinstance(current.get(config_path[-1]), dict):
|
|
2783
|
+
current[config_path[-1]] = value
|
|
2770
2784
|
|
|
2771
2785
|
return env_config
|
|
2772
2786
|
|
|
@@ -1566,7 +1566,14 @@ def extension_set_priority(
|
|
|
1566
1566
|
raw_priority = metadata.get("priority")
|
|
1567
1567
|
# Only skip if the stored value is already a valid int equal to requested priority
|
|
1568
1568
|
# This ensures corrupted values (e.g., "high") get repaired even when setting to default (10)
|
|
1569
|
-
|
|
1569
|
+
# A bool is an int in Python (isinstance(True, int) is True), so exclude it explicitly —
|
|
1570
|
+
# mirroring normalize_priority's bool guard — otherwise a corrupted True/False priority
|
|
1571
|
+
# equals 1/0 here and is never repaired.
|
|
1572
|
+
if (
|
|
1573
|
+
isinstance(raw_priority, int)
|
|
1574
|
+
and not isinstance(raw_priority, bool)
|
|
1575
|
+
and raw_priority == priority
|
|
1576
|
+
):
|
|
1570
1577
|
console.print(f"[yellow]Extension '{_escape_markup(str(display_name))}' already has priority {priority}[/yellow]")
|
|
1571
1578
|
raise typer.Exit(0)
|
|
1572
1579
|
|
|
@@ -469,7 +469,14 @@ def preset_set_priority(
|
|
|
469
469
|
raw_priority = metadata.get("priority")
|
|
470
470
|
# Only skip if the stored value is already a valid int equal to requested priority
|
|
471
471
|
# This ensures corrupted values (e.g., "high") get repaired even when setting to default (10)
|
|
472
|
-
|
|
472
|
+
# A bool is an int in Python (isinstance(True, int) is True), so exclude it explicitly —
|
|
473
|
+
# mirroring normalize_priority's bool guard — otherwise a corrupted True/False priority
|
|
474
|
+
# equals 1/0 here and is never repaired.
|
|
475
|
+
if (
|
|
476
|
+
isinstance(raw_priority, int)
|
|
477
|
+
and not isinstance(raw_priority, bool)
|
|
478
|
+
and raw_priority == priority
|
|
479
|
+
):
|
|
473
480
|
console.print(f"[yellow]Preset '{preset_id}' already has priority {priority}[/yellow]")
|
|
474
481
|
raise typer.Exit(0)
|
|
475
482
|
|
|
@@ -76,8 +76,16 @@ class WorkflowRegistry:
|
|
|
76
76
|
if self.registry_path.exists():
|
|
77
77
|
try:
|
|
78
78
|
with open(self.registry_path, encoding="utf-8") as f:
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
data = json.load(f)
|
|
80
|
+
# Validate shape: must be a dict with a dict "workflows" field,
|
|
81
|
+
# otherwise every method that indexes data["workflows"] crashes.
|
|
82
|
+
# Mirrors StepRegistry._load.
|
|
83
|
+
if not isinstance(data, dict):
|
|
84
|
+
return {"schema_version": self.SCHEMA_VERSION, "workflows": {}}
|
|
85
|
+
if not isinstance(data.get("workflows"), dict):
|
|
86
|
+
data["workflows"] = {}
|
|
87
|
+
return data
|
|
88
|
+
except (json.JSONDecodeError, ValueError, OSError, UnicodeError):
|
|
81
89
|
# Corrupted registry file — reset to default
|
|
82
90
|
return {"schema_version": self.SCHEMA_VERSION, "workflows": {}}
|
|
83
91
|
return {"schema_version": self.SCHEMA_VERSION, "workflows": {}}
|
|
@@ -438,9 +446,9 @@ class WorkflowCatalog:
|
|
|
438
446
|
q = query.lower()
|
|
439
447
|
searchable = " ".join(
|
|
440
448
|
[
|
|
441
|
-
wf_data.get("name"
|
|
442
|
-
wf_data.get("description"
|
|
443
|
-
wf_data.get("id"
|
|
449
|
+
str(wf_data.get("name") or ""),
|
|
450
|
+
str(wf_data.get("description") or ""),
|
|
451
|
+
str(wf_data.get("id") or ""),
|
|
444
452
|
]
|
|
445
453
|
).lower()
|
|
446
454
|
if q not in searchable:
|
|
@@ -982,7 +982,16 @@ class WorkflowEngine:
|
|
|
982
982
|
from .expressions import evaluate_condition
|
|
983
983
|
|
|
984
984
|
max_iters = step_config.get("max_iterations")
|
|
985
|
-
|
|
985
|
+
# A bool is an int in Python (isinstance(True, int) is True
|
|
986
|
+
# and True == 1), so a bool max_iterations would slip past
|
|
987
|
+
# the int check and cap the loop at range(0)==1 iteration
|
|
988
|
+
# instead of the default. Exclude bools, mirroring the
|
|
989
|
+
# while/do-while validators and the continue_on_error guard.
|
|
990
|
+
if (
|
|
991
|
+
isinstance(max_iters, bool)
|
|
992
|
+
or not isinstance(max_iters, int)
|
|
993
|
+
or max_iters < 1
|
|
994
|
+
):
|
|
986
995
|
max_iters = 10
|
|
987
996
|
condition = step_config.get("condition", False)
|
|
988
997
|
for _loop_iter in range(max_iters - 1):
|
|
@@ -175,7 +175,23 @@ def test_build_produces_artifact(project: Path):
|
|
|
175
175
|
assert len(artifacts) == 1
|
|
176
176
|
|
|
177
177
|
|
|
178
|
-
def
|
|
178
|
+
def _mock_manifest_download(monkeypatch, source_path: Path) -> None:
|
|
179
|
+
"""Mock the HTTPS manifest fetch to return a locally-authored manifest.
|
|
180
|
+
|
|
181
|
+
Catalog ``download_url``s are HTTPS-only, so ``info`` tests can no longer
|
|
182
|
+
point one at a local file. Patch ``_download_manifest`` to return the
|
|
183
|
+
manifest parsed from *source_path* (a bundle.yml or a .zip artifact),
|
|
184
|
+
exercising ``info``'s expansion without a network call.
|
|
185
|
+
"""
|
|
186
|
+
from specify_cli.commands.bundle import _local_manifest_source
|
|
187
|
+
|
|
188
|
+
monkeypatch.setattr(
|
|
189
|
+
"specify_cli.commands.bundle._download_manifest",
|
|
190
|
+
lambda resolved, *, offline: _local_manifest_source(str(source_path)),
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def test_info_expands_full_component_set(project: Path, monkeypatch):
|
|
179
195
|
bundle_dir = project / "src-bundle"
|
|
180
196
|
bundle_dir.mkdir()
|
|
181
197
|
(bundle_dir / "bundle.yml").write_text(
|
|
@@ -183,13 +199,14 @@ def test_info_expands_full_component_set(project: Path):
|
|
|
183
199
|
)
|
|
184
200
|
catalog = project / "local-catalog.json"
|
|
185
201
|
entry = catalog_entry_dict(
|
|
186
|
-
"demo-bundle", download_url=
|
|
202
|
+
"demo-bundle", download_url="https://example.com/demo-bundle.zip"
|
|
187
203
|
)
|
|
188
204
|
write_catalog_file(catalog, {"demo-bundle": entry})
|
|
189
205
|
added = runner.invoke(
|
|
190
206
|
app, ["bundle", "catalog", "add", str(catalog), "--id", "local"]
|
|
191
207
|
)
|
|
192
208
|
assert added.exit_code == 0, added.output
|
|
209
|
+
_mock_manifest_download(monkeypatch, bundle_dir / "bundle.yml")
|
|
193
210
|
|
|
194
211
|
result = runner.invoke(app, ["bundle", "info", "demo-bundle", "--json", "--offline"])
|
|
195
212
|
assert result.exit_code == 0, result.output
|
|
@@ -207,7 +224,7 @@ def test_info_expands_full_component_set(project: Path):
|
|
|
207
224
|
assert "Trust" in text.output
|
|
208
225
|
|
|
209
226
|
|
|
210
|
-
def test_info_expands_discovery_only_bundle(project: Path):
|
|
227
|
+
def test_info_expands_discovery_only_bundle(project: Path, monkeypatch):
|
|
211
228
|
# Discovery-only bundles must still be fully inspectable via `info`;
|
|
212
229
|
# only `install` is refused for them.
|
|
213
230
|
bundle_dir = project / "disc-bundle"
|
|
@@ -217,7 +234,7 @@ def test_info_expands_discovery_only_bundle(project: Path):
|
|
|
217
234
|
)
|
|
218
235
|
catalog = project / "disc-catalog.json"
|
|
219
236
|
entry = catalog_entry_dict(
|
|
220
|
-
"demo-bundle", download_url=
|
|
237
|
+
"demo-bundle", download_url="https://example.com/demo-bundle.zip"
|
|
221
238
|
)
|
|
222
239
|
write_catalog_file(catalog, {"demo-bundle": entry})
|
|
223
240
|
config = {
|
|
@@ -230,6 +247,7 @@ def test_info_expands_discovery_only_bundle(project: Path):
|
|
|
230
247
|
(project / ".specify" / "bundle-catalogs.yml").write_text(
|
|
231
248
|
yaml.safe_dump(config), encoding="utf-8"
|
|
232
249
|
)
|
|
250
|
+
_mock_manifest_download(monkeypatch, bundle_dir / "bundle.yml")
|
|
233
251
|
result = runner.invoke(app, ["bundle", "info", "demo-bundle", "--json", "--offline"])
|
|
234
252
|
assert result.exit_code == 0, result.output
|
|
235
253
|
payload = json.loads(result.output)
|
|
@@ -237,8 +255,9 @@ def test_info_expands_discovery_only_bundle(project: Path):
|
|
|
237
255
|
assert ("extensions", "ext-a") in components
|
|
238
256
|
|
|
239
257
|
|
|
240
|
-
def
|
|
241
|
-
# A
|
|
258
|
+
def test_info_expands_zip_sourced_bundle(project: Path, monkeypatch):
|
|
259
|
+
# A .zip artifact is extracted to read bundle.yml; info expands it. (The
|
|
260
|
+
# download itself is HTTPS-only now and mocked here — see contract note.)
|
|
242
261
|
bundle_dir = project / "zip-src"
|
|
243
262
|
bundle_dir.mkdir()
|
|
244
263
|
(bundle_dir / "bundle.yml").write_text(
|
|
@@ -249,12 +268,15 @@ def test_info_resolves_local_zip_download_url(project: Path):
|
|
|
249
268
|
catalog = project / "zip-catalog.json"
|
|
250
269
|
write_catalog_file(
|
|
251
270
|
catalog,
|
|
252
|
-
{"demo-bundle": catalog_entry_dict(
|
|
271
|
+
{"demo-bundle": catalog_entry_dict(
|
|
272
|
+
"demo-bundle", download_url="https://example.com/demo-bundle.zip"
|
|
273
|
+
)},
|
|
253
274
|
)
|
|
254
275
|
added = runner.invoke(
|
|
255
276
|
app, ["bundle", "catalog", "add", str(catalog), "--id", "local"]
|
|
256
277
|
)
|
|
257
278
|
assert added.exit_code == 0, added.output
|
|
279
|
+
_mock_manifest_download(monkeypatch, artifact)
|
|
258
280
|
result = runner.invoke(app, ["bundle", "info", "demo-bundle", "--json", "--offline"])
|
|
259
281
|
assert result.exit_code == 0, result.output
|
|
260
282
|
payload = json.loads(result.output)
|
|
@@ -112,3 +112,62 @@ def test_install_bundled_extension_from_zip_offline(tmp_path: Path):
|
|
|
112
112
|
assert not ExtensionManager(project).registry.is_installed("agent-context")
|
|
113
113
|
finally:
|
|
114
114
|
os.chdir(previous)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_download_manifest_rejects_file_url(tmp_path: Path):
|
|
118
|
+
"""A catalog ``file://`` download_url is rejected — catalog URLs are
|
|
119
|
+
HTTPS-only, matching extensions/presets/workflows. Disk installs go through
|
|
120
|
+
the positional path (see the local-source tests above), not download_url.
|
|
121
|
+
"""
|
|
122
|
+
from types import SimpleNamespace
|
|
123
|
+
|
|
124
|
+
from specify_cli.commands.bundle import _download_manifest
|
|
125
|
+
|
|
126
|
+
manifest_path = write_manifest(tmp_path / "my bundles")
|
|
127
|
+
resolved = SimpleNamespace(
|
|
128
|
+
entry=SimpleNamespace(id="demo-bundle", download_url=manifest_path.as_uri())
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
with pytest.raises(BundlerError, match="bundle install"):
|
|
132
|
+
_download_manifest(resolved, offline=True)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_download_manifest_rejects_bare_path(tmp_path: Path):
|
|
136
|
+
"""A bare filesystem path download_url is likewise rejected."""
|
|
137
|
+
from types import SimpleNamespace
|
|
138
|
+
|
|
139
|
+
from specify_cli.commands.bundle import _download_manifest
|
|
140
|
+
|
|
141
|
+
manifest_path = write_manifest(tmp_path / "plain")
|
|
142
|
+
resolved = SimpleNamespace(
|
|
143
|
+
entry=SimpleNamespace(id="demo-bundle", download_url=str(manifest_path))
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
with pytest.raises(BundlerError, match="bundle install"):
|
|
147
|
+
_download_manifest(resolved, offline=True)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_local_install_still_resolves_via_positional_path(tmp_path: Path):
|
|
151
|
+
"""The supported local route — a positional path, not a download_url —
|
|
152
|
+
still resolves the manifest via _local_manifest_source."""
|
|
153
|
+
manifest_path = write_manifest(tmp_path / "my bundles")
|
|
154
|
+
manifest = _local_manifest_source(str(manifest_path))
|
|
155
|
+
assert manifest is not None
|
|
156
|
+
assert manifest.bundle.id == "demo-bundle"
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def test_download_manifest_rejects_non_https_url_even_offline(tmp_path: Path):
|
|
160
|
+
"""A non-HTTPS download_url must report the HTTPS problem, not a misleading
|
|
161
|
+
'Network access disabled', even under --offline (scheme is validated before
|
|
162
|
+
the offline gate)."""
|
|
163
|
+
from types import SimpleNamespace
|
|
164
|
+
|
|
165
|
+
from specify_cli.commands.bundle import _download_manifest
|
|
166
|
+
|
|
167
|
+
resolved = SimpleNamespace(
|
|
168
|
+
entry=SimpleNamespace(
|
|
169
|
+
id="demo-bundle", download_url="http://example.com/bundle.zip"
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
with pytest.raises(BundlerError, match="HTTPS"):
|
|
173
|
+
_download_manifest(resolved, offline=True)
|
|
@@ -6424,6 +6424,42 @@ class TestExtensionPriorityCLI:
|
|
|
6424
6424
|
plain = strip_ansi(result.output)
|
|
6425
6425
|
assert "already has priority 5" in plain
|
|
6426
6426
|
|
|
6427
|
+
def test_set_priority_repairs_corrupted_bool(self, extension_dir, project_dir):
|
|
6428
|
+
"""A corrupted boolean priority must be repaired, not skipped.
|
|
6429
|
+
|
|
6430
|
+
``isinstance(True, int)`` is True and ``True == 1`` in Python, so a
|
|
6431
|
+
stored ``True`` priority would short-circuit the ``already has
|
|
6432
|
+
priority 1`` skip path and never get rewritten to a real int —
|
|
6433
|
+
contradicting the comment that promises corrupted values are
|
|
6434
|
+
repaired. The guard must exclude bools (like normalize_priority).
|
|
6435
|
+
"""
|
|
6436
|
+
from typer.testing import CliRunner
|
|
6437
|
+
from unittest.mock import patch
|
|
6438
|
+
from specify_cli import app
|
|
6439
|
+
|
|
6440
|
+
runner = CliRunner()
|
|
6441
|
+
|
|
6442
|
+
manager = ExtensionManager(project_dir)
|
|
6443
|
+
manager.install_from_directory(
|
|
6444
|
+
extension_dir, "0.1.0", register_commands=False, priority=5
|
|
6445
|
+
)
|
|
6446
|
+
# Inject a corrupted boolean priority (True == 1).
|
|
6447
|
+
manager.registry.update("test-ext", {"priority": True})
|
|
6448
|
+
|
|
6449
|
+
with patch.object(Path, "cwd", return_value=project_dir):
|
|
6450
|
+
result = runner.invoke(app, ["extension", "set-priority", "test-ext", "1"])
|
|
6451
|
+
|
|
6452
|
+
assert result.exit_code == 0, result.output
|
|
6453
|
+
plain = strip_ansi(result.output)
|
|
6454
|
+
# The corrupted bool must be repaired, not reported as already-set.
|
|
6455
|
+
assert "already has priority" not in plain
|
|
6456
|
+
assert "priority changed" in plain
|
|
6457
|
+
|
|
6458
|
+
# The stored value is now a real int, not a bool.
|
|
6459
|
+
reloaded = ExtensionManager(project_dir).registry.get("test-ext")
|
|
6460
|
+
assert reloaded["priority"] == 1
|
|
6461
|
+
assert not isinstance(reloaded["priority"], bool)
|
|
6462
|
+
|
|
6427
6463
|
def test_set_priority_invalid_value(self, extension_dir, project_dir):
|
|
6428
6464
|
"""Test set-priority rejects invalid priority values."""
|
|
6429
6465
|
from typer.testing import CliRunner
|
|
@@ -7628,3 +7664,56 @@ class TestConfigManagerNonMappingYaml:
|
|
|
7628
7664
|
(ext_dir / "jira-config.yml").write_text("just a string\n", encoding="utf-8")
|
|
7629
7665
|
executor = HookExecutor(tmp_path)
|
|
7630
7666
|
assert executor._evaluate_condition("config.x is set", "jira") is False
|
|
7667
|
+
|
|
7668
|
+
|
|
7669
|
+
class TestConfigManagerEnvPrefixCollision:
|
|
7670
|
+
"""Prefix-colliding env vars must not crash or clobber nested config."""
|
|
7671
|
+
|
|
7672
|
+
def test_scalar_then_nested_yields_nested(self, tmp_path, monkeypatch):
|
|
7673
|
+
"""SPECKIT_X_CONNECTION=x then SPECKIT_X_CONNECTION_URL=y.
|
|
7674
|
+
|
|
7675
|
+
The scalar-first order previously raised TypeError ('str' object
|
|
7676
|
+
does not support item assignment) when the walk indexed into 'x'.
|
|
7677
|
+
"""
|
|
7678
|
+
monkeypatch.setenv("SPECKIT_TESTEXT_CONNECTION", "x")
|
|
7679
|
+
monkeypatch.setenv("SPECKIT_TESTEXT_CONNECTION_URL", "y")
|
|
7680
|
+
cm = ConfigManager(tmp_path, "testext")
|
|
7681
|
+
assert cm._get_env_config() == {"connection": {"url": "y"}}
|
|
7682
|
+
|
|
7683
|
+
def test_nested_then_scalar_does_not_clobber(self, tmp_path, monkeypatch):
|
|
7684
|
+
"""Reverse order previously returned {'connection': 'x'}, losing url."""
|
|
7685
|
+
monkeypatch.setenv("SPECKIT_TESTEXT_CONNECTION_URL", "y")
|
|
7686
|
+
monkeypatch.setenv("SPECKIT_TESTEXT_CONNECTION", "x")
|
|
7687
|
+
cm = ConfigManager(tmp_path, "testext")
|
|
7688
|
+
assert cm._get_env_config() == {"connection": {"url": "y"}}
|
|
7689
|
+
|
|
7690
|
+
def test_colliding_env_does_not_disable_hook_condition(self, tmp_path, monkeypatch):
|
|
7691
|
+
"""`config.connection.url is set` must stay True under colliding env.
|
|
7692
|
+
|
|
7693
|
+
Before the fix the TypeError propagated into should_execute_hook's
|
|
7694
|
+
blanket `except Exception: return False`, silently disabling the hook.
|
|
7695
|
+
"""
|
|
7696
|
+
ext_dir = tmp_path / ".specify" / "extensions" / "testext"
|
|
7697
|
+
ext_dir.mkdir(parents=True)
|
|
7698
|
+
(ext_dir / "testext-config.yml").write_text(
|
|
7699
|
+
"connection:\n url: https://example.com\n", encoding="utf-8"
|
|
7700
|
+
)
|
|
7701
|
+
monkeypatch.setenv("SPECKIT_TESTEXT_CONNECTION", "x")
|
|
7702
|
+
monkeypatch.setenv("SPECKIT_TESTEXT_CONNECTION_URL", "y")
|
|
7703
|
+
executor = HookExecutor(tmp_path)
|
|
7704
|
+
# Exercise the public API: before the fix the TypeError was swallowed
|
|
7705
|
+
# by should_execute_hook's `except Exception: return False`, so the
|
|
7706
|
+
# hook was silently disabled (False); after the fix it returns True.
|
|
7707
|
+
assert executor.should_execute_hook(
|
|
7708
|
+
{"condition": "config.connection.url is set", "extension": "testext"}
|
|
7709
|
+
) is True
|
|
7710
|
+
|
|
7711
|
+
def test_malformed_env_names_ignored(self, tmp_path, monkeypatch):
|
|
7712
|
+
"""A name with no key (SPECKIT_X_) or empty parts (consecutive
|
|
7713
|
+
underscores) must not create an entry under an empty key."""
|
|
7714
|
+
monkeypatch.setenv("SPECKIT_TESTEXT_", "orphan") # no key at all
|
|
7715
|
+
monkeypatch.setenv("SPECKIT_TESTEXT_A__B", "z") # empty middle part
|
|
7716
|
+
cm = ConfigManager(tmp_path, "testext")
|
|
7717
|
+
cfg = cm._get_env_config()
|
|
7718
|
+
assert "" not in cfg
|
|
7719
|
+
assert cfg == {"a": {"b": "z"}}
|
|
@@ -4060,6 +4060,40 @@ class TestPresetSetPriority:
|
|
|
4060
4060
|
plain = strip_ansi(result.output)
|
|
4061
4061
|
assert "already has priority 5" in plain
|
|
4062
4062
|
|
|
4063
|
+
def test_set_priority_repairs_corrupted_bool(self, project_dir, pack_dir):
|
|
4064
|
+
"""A corrupted boolean priority must be repaired, not skipped.
|
|
4065
|
+
|
|
4066
|
+
``isinstance(True, int)`` is True and ``True == 1`` in Python, so a
|
|
4067
|
+
stored ``True`` priority would short-circuit the ``already has
|
|
4068
|
+
priority 1`` skip path and never get rewritten to a real int —
|
|
4069
|
+
contradicting the comment that promises corrupted values are
|
|
4070
|
+
repaired. The guard must exclude bools (like normalize_priority).
|
|
4071
|
+
"""
|
|
4072
|
+
from typer.testing import CliRunner
|
|
4073
|
+
from unittest.mock import patch
|
|
4074
|
+
from specify_cli import app
|
|
4075
|
+
|
|
4076
|
+
runner = CliRunner()
|
|
4077
|
+
|
|
4078
|
+
manager = PresetManager(project_dir)
|
|
4079
|
+
manager.install_from_directory(pack_dir, "0.1.5", priority=5)
|
|
4080
|
+
# Inject a corrupted boolean priority (True == 1).
|
|
4081
|
+
manager.registry.update("test-pack", {"priority": True})
|
|
4082
|
+
|
|
4083
|
+
with patch.object(Path, "cwd", return_value=project_dir):
|
|
4084
|
+
result = runner.invoke(app, ["preset", "set-priority", "test-pack", "1"])
|
|
4085
|
+
|
|
4086
|
+
assert result.exit_code == 0, result.output
|
|
4087
|
+
plain = strip_ansi(result.output)
|
|
4088
|
+
# The corrupted bool must be repaired, not reported as already-set.
|
|
4089
|
+
assert "already has priority" not in plain
|
|
4090
|
+
assert "priority changed" in plain
|
|
4091
|
+
|
|
4092
|
+
# The stored value is now a real int, not a bool.
|
|
4093
|
+
reloaded = PresetManager(project_dir).registry.get("test-pack")
|
|
4094
|
+
assert reloaded["priority"] == 1
|
|
4095
|
+
assert not isinstance(reloaded["priority"], bool)
|
|
4096
|
+
|
|
4063
4097
|
def test_set_priority_invalid_value(self, project_dir, pack_dir):
|
|
4064
4098
|
"""Test set-priority rejects invalid priority values."""
|
|
4065
4099
|
from typer.testing import CliRunner
|