hyperi-ci 2.2.1__tar.gz → 2.2.2__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.
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/CHANGELOG.md +8 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/PKG-INFO +1 -1
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/TODO.md +18 -23
- hyperi_ci-2.2.2/VERSION +1 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/config/defaults.yaml +21 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/config.py +29 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/dispatch.py +24 -1
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/init.py +6 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_config.py +69 -1
- hyperi_ci-2.2.2/tests/unit/test_dispatch_status.py +96 -0
- hyperi_ci-2.2.2/uv.lock +878 -0
- hyperi_ci-2.2.1/VERSION +0 -1
- hyperi_ci-2.2.1/uv.lock +0 -718
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/.ai-version +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/commands/doco.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/commands/load.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/commands/review.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/commands/save.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/commands/setup-claude.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/commands/simplify.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/commands/standards.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/UNIVERSAL.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/ai-conduct.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/ansible.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/bash.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/chars-policy.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/ci.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/clickhouse-sql.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/code-header.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/code-style.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/config-and-logging.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/cpp.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/design-principles.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/dfe-metrics.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/docker.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/error-handling.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/git.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/golang.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/issue-management.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/k8s.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/licensing.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/mocks-policy.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/pki.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/python.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/repo-naming.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/rust.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/security.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/sql-clickhouse.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/terraform.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/testing.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/typescript.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/rules/universal.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/settings.json +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/skills/ai-common/SKILL.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/skills/ai-guidelines/SKILL.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/skills/bleeding-edge/SKILL.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/skills/ci-check/SKILL.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/skills/ci-logs/SKILL.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/skills/ci-watch/SKILL.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/skills/deps/SKILL.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/skills/docs-audit/SKILL.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/skills/documentation/SKILL.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/skills/python/SKILL.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/skills/release/SKILL.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/skills/standards/SKILL.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/skills/test-review/SKILL.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.claude/skills/verification/SKILL.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.gitattributes +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.github/actions/predict-version/action.yml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.github/workflows/_release-tail.yml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.github/workflows/ci.yml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.github/workflows/go-ci.yml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.github/workflows/python-ci.yml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.github/workflows/rust-ci.yml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.github/workflows/ts-ci.yml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.gitignore +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.gitmodules +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.hyperi-ci.yaml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.mcp.json +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/.releaserc.yaml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/AI-TRAINING-POLICY.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/CLAUDE.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/LICENSE +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/README.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/STATE.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/config/commit-types.yaml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/config/org.yaml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/config/runners.yaml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/config/secrets-access.yaml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/config/versions.yaml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/docs/ARC-RUNNERS.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/docs/ARCHITECTURE.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/docs/CI-LESSONS.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/docs/DESIGN.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/docs/JFROG-MIGRATION.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/docs/MIGRATION-GUIDE.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/docs/PGO-WORKLOAD-GUIDE.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/docs/deployment-contract.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/docs/migration/deployment-contract-tier3.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/docs/rust.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/docs/typescript.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/pyproject.toml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/renovate.json +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/robots.txt +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/scripts/pre-commit-versions.sh +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/scripts/setup-rust-dev.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/scripts/sync-secrets-access.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/scripts/update-versions.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/__init__.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/cli.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/common.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/config/native-deps/golang.yaml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/config/native-deps/python.yaml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/config/native-deps/rust.yaml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/config/native-deps/typescript.yaml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/config/org.yaml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/config/toolchains/gcc.yaml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/config/toolchains/llvm.yaml +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/container/__init__.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/container/binary_stage.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/container/build.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/container/compose.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/container/detect.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/container/labels.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/container/manifest.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/container/registry.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/container/stage.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/container/templates.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/deployment/__init__.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/deployment/cli.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/deployment/contract.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/deployment/detect.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/deployment/registry.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/deployment/scaffold.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/deployment/stage.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/detect.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/gh.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/install_deps.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/__init__.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/_build_common.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/golang/__init__.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/golang/build.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/golang/publish.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/golang/quality.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/golang/test.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/python/__init__.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/python/build.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/python/publish.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/python/quality.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/python/test.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/quality_common.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/rust/__init__.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/rust/build.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/rust/optimize.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/rust/pgo.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/rust/publish.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/rust/quality.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/rust/test.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/typescript/__init__.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/typescript/_common.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/typescript/build.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/typescript/install_deps.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/typescript/publish.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/typescript/quality.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/typescript/test.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/logs.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/migrate.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/native_deps.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/publish/__init__.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/publish/binaries.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/publish/dispatch.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/publish_binaries.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/push.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/quality/__init__.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/quality/commit_validation.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/quality/gitleaks.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/release.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/trigger.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/upgrade.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/src/hyperi_ci/watch.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/templates/pgo-workload/README.md +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/templates/pgo-workload/grpc-server.sh +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/templates/pgo-workload/http-server.sh +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/templates/pgo-workload/kafka-consumer.sh +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/templates/pgo-workload/kafka-producer.sh +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/templates/pgo-workload/multi-protocol.sh +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/__init__.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/integration/__init__.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/integration/test_rust_build_optimize.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/__init__.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/deployment/__init__.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/deployment/test_contract.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/deployment/test_detect_tier.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/deployment/test_emit_artefacts_cli.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/deployment/test_registry_cascade.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/deployment/test_scaffold.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/deployment/test_stage.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_cli.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_commit_validation.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_common.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_container_binary_stage.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_container_build.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_container_compose.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_container_detect.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_container_labels.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_container_manifest.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_container_registry.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_container_stage.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_container_templates.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_detect.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_dispatch_alias.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_gh.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_init.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_migrate.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_native_deps.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_publish.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_push.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_rust_optimize.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_rust_pgo.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_rust_quality.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_typescript_quality.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_upgrade.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_watch.py +0 -0
- {hyperi_ci-2.2.1 → hyperi_ci-2.2.2}/tests/unit/test_workflow_consistency.py +0 -0
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## [2.2.2](https://github.com/hyperi-io/hyperi-ci/compare/v2.2.1...v2.2.2) (2026-05-13)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **config:** add project.status lifecycle field (information-only) ([a66a1a2](https://github.com/hyperi-io/hyperi-ci/commit/a66a1a23d911548078ebc62ba9995392ce9ec087))
|
|
7
|
+
* **dispatch:** log project.status as INFO with clarifier phrase, not WARN ([c9de7cd](https://github.com/hyperi-io/hyperi-ci/commit/c9de7cdcae47939b93a830987e59e13643516027))
|
|
8
|
+
|
|
1
9
|
## [2.2.1](https://github.com/hyperi-io/hyperi-ci/compare/v2.2.0...v2.2.1) (2026-05-08)
|
|
2
10
|
|
|
3
11
|
|
|
@@ -6,36 +6,22 @@ This is the **single source of truth** for all tasks and progress.
|
|
|
6
6
|
|
|
7
7
|
## Active Tasks
|
|
8
8
|
|
|
9
|
-
### CI consolidation (KISS) — predict-first plan-job per language
|
|
9
|
+
### CI consolidation (KISS) — predict-first plan-job per language ✅ DONE 2026-05-08
|
|
10
10
|
|
|
11
11
|
**Plan:** [`docs/superpowers/plans/2026-05-08-ci-consolidation-kiss.md`](docs/superpowers/plans/2026-05-08-ci-consolidation-kiss.md)
|
|
12
12
|
|
|
13
|
-
**Architecture:** [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)
|
|
13
|
+
**Architecture:** [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)
|
|
14
14
|
|
|
15
|
-
**
|
|
15
|
+
**Outcome:** v2.2.0 + v2.2.1 shipped to PyPI 2026-05-08. New plan-first gating validated on the most complex Rust consumer end-to-end. Python gate validated; full publish-canary on Python deferred to hyperi-pylib (clean library) instead of dfe-engine (in-progress feature branches).
|
|
16
16
|
|
|
17
|
-
**
|
|
17
|
+
**Canary results:**
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
**
|
|
22
|
-
|
|
23
|
-
- [x] **Canary 0: hyperi-ci's own `test-projects/`** — integration tests green; one stale spike-allocator test fixed (commit `a331a0b`).
|
|
24
|
-
- [x] **Canary 1a: dfe-loader chore-skip** — [run 25531398891](https://github.com/hyperi-io/dfe-loader/actions/runs/25531398891) — 16 seconds total, plan ✅, every other job skipped. **Killer feature validated.**
|
|
25
|
-
- [ ] **Canary 1b: dfe-loader publish-dispatch** — [run 25531808212](https://github.com/hyperi-io/dfe-loader/actions/runs/25531808212) in flight as of 2026-05-08T01:40Z. Plan/Quality/Test green; both Build (linux-amd64, linux-arm64) jobs in_progress. BOLT/PGO; expect 30-45 min.
|
|
19
|
+
- [x] **Canary 0: hyperi-ci's own `test-projects/`** — integration tests green (7 slow tests pass); one stale spike-allocator test fixed (commit `a331a0b`).
|
|
20
|
+
- [x] **Canary 1a: dfe-loader chore-skip** — [run 25531398891](https://github.com/hyperi-io/dfe-loader/actions/runs/25531398891) — **16 seconds total**, plan ✅, every other job skipped. **Killer feature validated.**
|
|
21
|
+
- [x] **Canary 1b: dfe-loader publish-dispatch** — [run 25531808212](https://github.com/hyperi-io/dfe-loader/actions/runs/25531808212) — **38 minutes total**, all 7 jobs ✅ green end-to-end (Plan, Quality, Test, Build linux-amd64, Build linux-arm64, Container, Tag & Publish). Multi-arch BOLT+PGO Rust publish chain proven on the most complex Rust consumer.
|
|
26
22
|
- [x] **Canary 2a: dfe-engine chore-skip** — [run 25531462469](https://github.com/hyperi-io/dfe-engine/actions/runs/25531462469) — 33 seconds, all heavy jobs skipped. **Gate validated.**
|
|
27
|
-
- [ ] **Canary 2b: dfe-engine publish-dispatch** — failed at quality
|
|
28
|
-
- [ ] **Canary 3 (
|
|
29
|
-
|
|
30
|
-
**Ordering update 2026-05-08:** Python canary order is hyperi-pylib FIRST (clean), then dfe-engine after merge coordination. dfe-engine is the most complex Python case but its current branch state makes it a poor first canary.
|
|
31
|
-
|
|
32
|
-
**Done means:**
|
|
33
|
-
|
|
34
|
-
- All 17 plan tasks checked off
|
|
35
|
-
- All four `<lang>-ci.yml` workflows match the same shape (verified by `tests/unit/test_workflow_consistency.py`)
|
|
36
|
-
- A chore: commit on each canary completes in <2 min, skips quality/test/build/container/publish
|
|
37
|
-
- A `Publish: true` commit on each canary still runs the full pipeline and ships to the right registries
|
|
38
|
-
- v2.2.0+ of hyperi-ci is published to PyPI
|
|
23
|
+
- [ ] **Canary 2b: dfe-engine publish-dispatch** — failed at quality on real pre-existing CVEs (pytest GHSA-6w46-j5rx-g56g, cryptography GHSA-p423-j2cm-9vmq). NOT a gate regression. Handed back to dfe-engine maintainers via [`dfe-engine/docs/superpowers/plans/2026-05-08-ci-canary-publish-fix.md`](../../dfe-engine/docs/superpowers/plans/2026-05-08-ci-canary-publish-fix.md). Blocked on Derek + Kaz merge coordination.
|
|
24
|
+
- [ ] **Canary 3 (Python publish-dispatch on hyperi-pylib)** — preferred Python canary: clean lib, no parallel WIP. User to drive directly.
|
|
39
25
|
|
|
40
26
|
**Bugs discovered during rollout (no SEP fields):**
|
|
41
27
|
|
|
@@ -43,6 +29,15 @@ This is the **single source of truth** for all tasks and progress.
|
|
|
43
29
|
- [x] `_release-tail.yml` expects a single `build-dist-*` artifact containing both `dist/` AND `ci-tmp/`. The plan's draft proposed splitting into separate artifacts which would have broken release-tail's download step. Combined-upload pattern preserved.
|
|
44
30
|
- [x] `ts-ci.yml` had a duplicate `setup:` job key (silent YAML override) and `test:` job missing `needs:`/`if:`. Fixed during Task 8.
|
|
45
31
|
- [x] `go-ci.yml` ran quality + test unconditionally before any gate decision (setup ran AFTER them). Fixed during Task 9.
|
|
32
|
+
- [x] `init.py` scaffold generated `workflow_dispatch:` with no `tag` input → `hyperi-ci publish` returned HTTP 422. Every project initialised with v2.2.0 inherited the bug. Fixed in v2.2.1 (commit `fa7d79b`) — surfaced by dfe-engine canary, fixed in dfe-engine consumer ci.yml AND in hyperi-ci scaffold.
|
|
33
|
+
- [x] `hyperi-ci watch <run-id>` invoked from a different repo's cwd silently 404'd 10× before giving up. Added `--repo / -R` flag in v2.2.1 (commit `c90c96a`) — surfaced by dfe-loader canary subagent.
|
|
34
|
+
|
|
35
|
+
**Versions shipped:**
|
|
36
|
+
|
|
37
|
+
- v2.2.0 ([run 25531099781](https://github.com/hyperi-io/hyperi-ci/actions/runs/25531099781)) — initial KISS rollout
|
|
38
|
+
- v2.2.1 ([run 25533443889](https://github.com/hyperi-io/hyperi-ci/actions/runs/25533443889)) — init.py + watch --repo + jemalloc test fixes
|
|
39
|
+
|
|
40
|
+
**Next:** monitor hyperi-pylib's Python canary (when user runs it) and dfe-engine's CVE-cleanup re-canary. If both come back green, the rollout is fully closed. If new bugs surface, fix and ship as v2.2.x.
|
|
46
41
|
|
|
47
42
|
---
|
|
48
43
|
|
hyperi_ci-2.2.2/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.2.2
|
|
@@ -13,6 +13,27 @@
|
|
|
13
13
|
# Values: python, typescript, golang, rust, none
|
|
14
14
|
language: none
|
|
15
15
|
|
|
16
|
+
# Project metadata
|
|
17
|
+
#
|
|
18
|
+
# project.status: lifecycle stage of THIS PROJECT (not of any given release).
|
|
19
|
+
# Information-only field — surfaced in `hyperi-ci config` and at stage
|
|
20
|
+
# startup in CI logs so consumers can immediately see "oh, it's not GA".
|
|
21
|
+
# Does not affect publish behaviour (that's `publish.channel`).
|
|
22
|
+
#
|
|
23
|
+
# Values:
|
|
24
|
+
# experimental — early R&D, no API commitment, expect anything
|
|
25
|
+
# alpha — usable, expect breaks, no semver guarantees
|
|
26
|
+
# beta — feature-stable, polishing, mild breaks possible
|
|
27
|
+
# ga — semver guarantees apply, breaking only on major bump
|
|
28
|
+
# legacy — still works, successor exists, plan migration
|
|
29
|
+
# deprecated — active sunset, do not adopt
|
|
30
|
+
#
|
|
31
|
+
# Unset = "not declared" — the log line says "(status not declared)" which
|
|
32
|
+
# is itself a useful signal. New projects scaffolded by `hyperi-ci init`
|
|
33
|
+
# default to `experimental`.
|
|
34
|
+
project:
|
|
35
|
+
status: ""
|
|
36
|
+
|
|
16
37
|
# Runner Settings
|
|
17
38
|
#
|
|
18
39
|
# mode: controls which GitHub Actions runners are used
|
|
@@ -22,6 +22,19 @@ import yaml
|
|
|
22
22
|
|
|
23
23
|
_CONFIG_DIR = Path(__file__).resolve().parent / "config"
|
|
24
24
|
|
|
25
|
+
# Lifecycle stages a project can declare via `project.status` in
|
|
26
|
+
# `.hyperi-ci.yaml`. Information-only — does not gate any behaviour.
|
|
27
|
+
# Empty string (default) means "not declared" — the field is optional.
|
|
28
|
+
# See defaults.yaml for what each stage means.
|
|
29
|
+
VALID_PROJECT_STATUSES: tuple[str, ...] = (
|
|
30
|
+
"experimental",
|
|
31
|
+
"alpha",
|
|
32
|
+
"beta",
|
|
33
|
+
"ga",
|
|
34
|
+
"legacy",
|
|
35
|
+
"deprecated",
|
|
36
|
+
)
|
|
37
|
+
|
|
25
38
|
# Re-exported for callers that just want the constant without going through
|
|
26
39
|
# the full CIConfig load (e.g. quality-stage drift checks). Authoritative
|
|
27
40
|
# value is defined alongside the Pydantic model that uses it as a field
|
|
@@ -229,6 +242,22 @@ def load_config(
|
|
|
229
242
|
publish.get("target", "oss") if isinstance(publish, dict) else "oss"
|
|
230
243
|
)
|
|
231
244
|
|
|
245
|
+
# Validate project.status if set. Warn on unknown values rather than
|
|
246
|
+
# failing — the field is information-only and a typo shouldn't break
|
|
247
|
+
# the build.
|
|
248
|
+
project = config.get("project", {})
|
|
249
|
+
if isinstance(project, dict):
|
|
250
|
+
status = str(project.get("status") or "").strip().lower()
|
|
251
|
+
if status and status not in VALID_PROJECT_STATUSES:
|
|
252
|
+
# Lazy import to avoid circular dep at module load.
|
|
253
|
+
from hyperi_ci.common import warn
|
|
254
|
+
|
|
255
|
+
warn(
|
|
256
|
+
f"Unknown project.status '{status}' — expected one of "
|
|
257
|
+
f"{', '.join(VALID_PROJECT_STATUSES)} (or unset). "
|
|
258
|
+
f"Treating as unset for logging purposes."
|
|
259
|
+
)
|
|
260
|
+
|
|
232
261
|
_config_cache = CIConfig(
|
|
233
262
|
language=config.get("language", "none"),
|
|
234
263
|
ci_min_python_version=config.get("ci_min_python_version", "3.9"),
|
|
@@ -29,7 +29,7 @@ from hyperi_ci.common import (
|
|
|
29
29
|
success,
|
|
30
30
|
warn,
|
|
31
31
|
)
|
|
32
|
-
from hyperi_ci.config import CIConfig, load_config
|
|
32
|
+
from hyperi_ci.config import VALID_PROJECT_STATUSES, CIConfig, load_config
|
|
33
33
|
from hyperi_ci.detect import detect_language
|
|
34
34
|
from hyperi_ci.quality import commit_validation, gitleaks
|
|
35
35
|
|
|
@@ -62,6 +62,19 @@ VALID_STAGES = (
|
|
|
62
62
|
"publish",
|
|
63
63
|
)
|
|
64
64
|
|
|
65
|
+
# Parenthetical clarifier appended to the "Project status: <X>" log line
|
|
66
|
+
# so a reader of the CI output immediately knows what each value means
|
|
67
|
+
# without having to consult the docs. `ga` and `legacy` defaults are
|
|
68
|
+
# bare — the clarifier carries the signal only where it adds something.
|
|
69
|
+
_STATUS_CLARIFIER: dict[str, str] = {
|
|
70
|
+
"experimental": " — pre-GA, no API commitment",
|
|
71
|
+
"alpha": " — pre-GA, expect breaks",
|
|
72
|
+
"beta": " — pre-GA, polishing",
|
|
73
|
+
"ga": "",
|
|
74
|
+
"legacy": " — being phased out, plan migration",
|
|
75
|
+
"deprecated": " — do not adopt, scheduled for removal",
|
|
76
|
+
}
|
|
77
|
+
|
|
65
78
|
# Languages that share a handler package. The left-hand name is what
|
|
66
79
|
# `detect_language()` returns (honest — describes what the project actually
|
|
67
80
|
# is); the right-hand name is the handler module to dispatch to. Keeping
|
|
@@ -387,6 +400,16 @@ def run_stage(
|
|
|
387
400
|
|
|
388
401
|
config = load_config(reload=True, project_dir=project_dir)
|
|
389
402
|
|
|
403
|
+
# Surface project lifecycle status so consumers reading CI logs can
|
|
404
|
+
# immediately see "oh, this isn't GA". Skipped silently when the
|
|
405
|
+
# field is unset — `.hyperi-ci.yaml` need not declare it. Always
|
|
406
|
+
# logged as INFO; the parenthetical clarifier on non-ga values
|
|
407
|
+
# carries the signal without elevating log level (a beta project
|
|
408
|
+
# isn't an error, just a fact).
|
|
409
|
+
status = str(config.get("project.status") or "").strip().lower()
|
|
410
|
+
if status in VALID_PROJECT_STATUSES:
|
|
411
|
+
info(f"Project status: {status}{_STATUS_CLARIFIER.get(status, '')}")
|
|
412
|
+
|
|
390
413
|
handler = _STAGE_HANDLERS[stage]
|
|
391
414
|
if stage == "build":
|
|
392
415
|
rc = handler(language, config, local=local)
|
|
@@ -158,6 +158,12 @@ def _render_hyperi_ci_yaml(
|
|
|
158
158
|
"""Render .hyperi-ci.yaml with language-specific defaults."""
|
|
159
159
|
config: dict = {
|
|
160
160
|
"language": language,
|
|
161
|
+
# Information-only: lifecycle stage of the project. Surfaced
|
|
162
|
+
# in CI logs and `hyperi-ci config`. Does not gate any
|
|
163
|
+
# behaviour. New projects default to `experimental` — bump as
|
|
164
|
+
# the project matures. Values: experimental | alpha | beta |
|
|
165
|
+
# ga | legacy | deprecated.
|
|
166
|
+
"project": {"status": "experimental"},
|
|
161
167
|
"quality": {"enabled": True},
|
|
162
168
|
"test": {"enabled": True},
|
|
163
169
|
"build": {"enabled": True, "strategies": ["native"]},
|
|
@@ -11,7 +11,13 @@ from pathlib import Path
|
|
|
11
11
|
|
|
12
12
|
import pytest
|
|
13
13
|
|
|
14
|
-
from hyperi_ci.config import
|
|
14
|
+
from hyperi_ci.config import (
|
|
15
|
+
VALID_PROJECT_STATUSES,
|
|
16
|
+
CIConfig,
|
|
17
|
+
_merge_deep,
|
|
18
|
+
_parse_env_value,
|
|
19
|
+
load_config,
|
|
20
|
+
)
|
|
15
21
|
|
|
16
22
|
|
|
17
23
|
class TestMergeDeep:
|
|
@@ -159,3 +165,65 @@ class TestLoadConfig:
|
|
|
159
165
|
monkeypatch.setenv("HYPERCI_LANGUAGE", "golang")
|
|
160
166
|
config = load_config(reload=True, project_dir=tmp_path)
|
|
161
167
|
assert config.get("language") == "golang"
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class TestProjectStatus:
|
|
171
|
+
"""`project.status` is an information-only lifecycle stage field.
|
|
172
|
+
|
|
173
|
+
Surfaced in CI logs and `hyperi-ci config`. Does not gate any
|
|
174
|
+
behaviour. Six valid values; unknown values warn but don't fail.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
def test_valid_statuses_enum(self) -> None:
|
|
178
|
+
# Lock the vocabulary so a rename/typo elsewhere can't silently
|
|
179
|
+
# break the contract every consumer's `.hyperi-ci.yaml` expects.
|
|
180
|
+
assert VALID_PROJECT_STATUSES == (
|
|
181
|
+
"experimental",
|
|
182
|
+
"alpha",
|
|
183
|
+
"beta",
|
|
184
|
+
"ga",
|
|
185
|
+
"legacy",
|
|
186
|
+
"deprecated",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def test_set_status_reads_back(self, tmp_path: Path) -> None:
|
|
190
|
+
(tmp_path / ".hyperi-ci.yaml").write_text(
|
|
191
|
+
"language: rust\nproject:\n status: beta\n",
|
|
192
|
+
)
|
|
193
|
+
import hyperi_ci.config as cfg_mod
|
|
194
|
+
|
|
195
|
+
cfg_mod._config_cache = None
|
|
196
|
+
config = load_config(reload=True, project_dir=tmp_path)
|
|
197
|
+
assert config.get("project.status") == "beta"
|
|
198
|
+
|
|
199
|
+
def test_unset_status_returns_empty_or_none(self, tmp_path: Path) -> None:
|
|
200
|
+
# Default value in defaults.yaml is "" (empty string) — meaning
|
|
201
|
+
# "not declared". Skipping the field in `.hyperi-ci.yaml`
|
|
202
|
+
# leaves the default in place.
|
|
203
|
+
(tmp_path / ".hyperi-ci.yaml").write_text("language: rust\n")
|
|
204
|
+
import hyperi_ci.config as cfg_mod
|
|
205
|
+
|
|
206
|
+
cfg_mod._config_cache = None
|
|
207
|
+
config = load_config(reload=True, project_dir=tmp_path)
|
|
208
|
+
# Either "" (default from defaults.yaml) or None (no key at all)
|
|
209
|
+
# is acceptable — both mean "not declared".
|
|
210
|
+
status = config.get("project.status")
|
|
211
|
+
assert status in ("", None)
|
|
212
|
+
|
|
213
|
+
def test_unknown_status_warns_but_loads(
|
|
214
|
+
self,
|
|
215
|
+
tmp_path: Path,
|
|
216
|
+
capsys: pytest.CaptureFixture[str],
|
|
217
|
+
) -> None:
|
|
218
|
+
(tmp_path / ".hyperi-ci.yaml").write_text(
|
|
219
|
+
"language: rust\nproject:\n status: stable\n",
|
|
220
|
+
)
|
|
221
|
+
import hyperi_ci.config as cfg_mod
|
|
222
|
+
|
|
223
|
+
cfg_mod._config_cache = None
|
|
224
|
+
config = load_config(reload=True, project_dir=tmp_path)
|
|
225
|
+
# Config must still load — typos can't break the build.
|
|
226
|
+
assert config.language == "rust"
|
|
227
|
+
# The unknown value is preserved in raw config so operators can
|
|
228
|
+
# see what they wrote; only the log line warns.
|
|
229
|
+
assert config.get("project.status") == "stable"
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Project: HyperI CI
|
|
2
|
+
# File: tests/unit/test_dispatch_status.py
|
|
3
|
+
# Purpose: Unit tests for project.status logging at stage start
|
|
4
|
+
#
|
|
5
|
+
# License: Proprietary — HYPERI PTY LIMITED
|
|
6
|
+
# Copyright: (c) 2026 HYPERI PTY LIMITED
|
|
7
|
+
"""Tests for the `Project status:` log line emitted by the dispatcher.
|
|
8
|
+
|
|
9
|
+
`project.status` is an information-only lifecycle field; the dispatcher
|
|
10
|
+
surfaces it at the top of every stage so log readers see what kind of
|
|
11
|
+
project they're looking at. Behaviour locked in here:
|
|
12
|
+
|
|
13
|
+
- Unset (default) → no log line emitted.
|
|
14
|
+
- Known status (any of VALID_PROJECT_STATUSES) → one INFO line with the
|
|
15
|
+
status and a clarifier phrase that explains what the status means.
|
|
16
|
+
- Unknown status (typo) → warned at config-load time and skipped by the
|
|
17
|
+
dispatcher's status block — verified in `test_config.py`.
|
|
18
|
+
|
|
19
|
+
The clarifier phrase is what makes non-GA stand out without elevating
|
|
20
|
+
log level. A line that reads `Project status: beta — pre-GA, polishing`
|
|
21
|
+
is unmistakable in a wall of INFO without being a WARN.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import pytest
|
|
27
|
+
|
|
28
|
+
from hyperi_ci.dispatch import _STATUS_CLARIFIER
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TestStatusClarifierMap:
|
|
32
|
+
"""The clarifier map drives the user-visible signal — lock it in."""
|
|
33
|
+
|
|
34
|
+
def test_ga_has_no_clarifier(self) -> None:
|
|
35
|
+
# GA is the unmarked default — clarifier adds nothing, so the
|
|
36
|
+
# line reads simply "Project status: ga".
|
|
37
|
+
assert _STATUS_CLARIFIER["ga"] == ""
|
|
38
|
+
|
|
39
|
+
def test_pre_ga_statuses_say_pre_ga(self) -> None:
|
|
40
|
+
for pre_ga in ("experimental", "alpha", "beta"):
|
|
41
|
+
assert "pre-GA" in _STATUS_CLARIFIER[pre_ga], (
|
|
42
|
+
f"{pre_ga} clarifier missing 'pre-GA' marker"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def test_legacy_signals_migration(self) -> None:
|
|
46
|
+
assert "phased out" in _STATUS_CLARIFIER["legacy"]
|
|
47
|
+
assert "migration" in _STATUS_CLARIFIER["legacy"]
|
|
48
|
+
|
|
49
|
+
def test_deprecated_says_do_not_adopt(self) -> None:
|
|
50
|
+
assert "do not adopt" in _STATUS_CLARIFIER["deprecated"]
|
|
51
|
+
|
|
52
|
+
def test_all_valid_statuses_have_clarifier_entry(self) -> None:
|
|
53
|
+
# If we add a new status to the enum, this test forces us to
|
|
54
|
+
# decide what its clarifier phrase says rather than silently
|
|
55
|
+
# falling through to an empty default.
|
|
56
|
+
from hyperi_ci.config import VALID_PROJECT_STATUSES
|
|
57
|
+
|
|
58
|
+
for status in VALID_PROJECT_STATUSES:
|
|
59
|
+
assert status in _STATUS_CLARIFIER, (
|
|
60
|
+
f"{status} added to VALID_PROJECT_STATUSES but not to "
|
|
61
|
+
f"_STATUS_CLARIFIER — decide what the log line says"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def test_clarifier_has_no_warn_style_words(self) -> None:
|
|
65
|
+
# The whole point of the redesign: clarifier carries signal
|
|
66
|
+
# without being alarming. Reject "WARNING", "ERROR", "DANGER",
|
|
67
|
+
# "DO NOT USE" — these elevate tone past the info level the
|
|
68
|
+
# value deserves.
|
|
69
|
+
forbidden = ("WARNING", "ERROR", "DANGER", "DO NOT USE")
|
|
70
|
+
for status, phrase in _STATUS_CLARIFIER.items():
|
|
71
|
+
for word in forbidden:
|
|
72
|
+
assert word.lower() not in phrase.lower(), (
|
|
73
|
+
f"{status} clarifier '{phrase}' contains alarming "
|
|
74
|
+
f"word '{word}' — keep the tone at info level"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class TestStatusLineFormat:
|
|
79
|
+
"""Format the dispatcher emits: 'Project status: <value><clarifier>'."""
|
|
80
|
+
|
|
81
|
+
@pytest.mark.parametrize(
|
|
82
|
+
"status,expected_substring",
|
|
83
|
+
[
|
|
84
|
+
("ga", "Project status: ga"),
|
|
85
|
+
("beta", "Project status: beta — pre-GA, polishing"),
|
|
86
|
+
("legacy", "Project status: legacy — being phased out, plan migration"),
|
|
87
|
+
],
|
|
88
|
+
)
|
|
89
|
+
def test_format_matches_clarifier_map(
|
|
90
|
+
self, status: str, expected_substring: str
|
|
91
|
+
) -> None:
|
|
92
|
+
# Construct the exact line the dispatcher emits so the format
|
|
93
|
+
# contract is locked. If someone changes the prefix to
|
|
94
|
+
# "Lifecycle:" or drops the em-dash, this test catches it.
|
|
95
|
+
line = f"Project status: {status}{_STATUS_CLARIFIER[status]}"
|
|
96
|
+
assert expected_substring in line
|