hyperi-ci 2.2.0__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.0 → hyperi_ci-2.2.2}/CHANGELOG.md +17 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/PKG-INFO +1 -1
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/STATE.md +31 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/TODO.md +53 -16
- hyperi_ci-2.2.2/VERSION +1 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/ARCHITECTURE.md +17 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/cli.py +13 -1
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/config/defaults.yaml +21 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/config.py +29 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/dispatch.py +24 -1
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/gh.py +6 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/init.py +14 -1
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/watch.py +31 -19
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/integration/test_rust_build_optimize.py +8 -5
- {hyperi_ci-2.2.0 → 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.0 → hyperi_ci-2.2.2}/tests/unit/test_init.py +17 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_watch.py +36 -0
- hyperi_ci-2.2.2/uv.lock +878 -0
- hyperi_ci-2.2.0/VERSION +0 -1
- hyperi_ci-2.2.0/uv.lock +0 -718
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/.ai-version +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/commands/doco.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/commands/load.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/commands/review.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/commands/save.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/commands/setup-claude.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/commands/simplify.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/commands/standards.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/UNIVERSAL.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/ai-conduct.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/ansible.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/bash.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/chars-policy.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/ci.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/clickhouse-sql.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/code-header.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/code-style.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/config-and-logging.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/cpp.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/design-principles.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/dfe-metrics.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/docker.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/error-handling.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/git.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/golang.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/issue-management.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/k8s.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/licensing.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/mocks-policy.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/pki.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/python.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/repo-naming.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/rust.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/security.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/sql-clickhouse.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/terraform.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/testing.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/typescript.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/universal.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/settings.json +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/ai-common/SKILL.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/ai-guidelines/SKILL.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/bleeding-edge/SKILL.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/ci-check/SKILL.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/ci-logs/SKILL.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/ci-watch/SKILL.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/deps/SKILL.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/docs-audit/SKILL.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/documentation/SKILL.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/python/SKILL.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/release/SKILL.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/standards/SKILL.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/test-review/SKILL.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/verification/SKILL.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.gitattributes +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.github/actions/predict-version/action.yml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.github/workflows/_release-tail.yml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.github/workflows/ci.yml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.github/workflows/go-ci.yml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.github/workflows/python-ci.yml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.github/workflows/rust-ci.yml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.github/workflows/ts-ci.yml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.gitignore +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.gitmodules +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.hyperi-ci.yaml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.mcp.json +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.releaserc.yaml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/AI-TRAINING-POLICY.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/CLAUDE.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/LICENSE +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/README.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/config/commit-types.yaml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/config/org.yaml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/config/runners.yaml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/config/secrets-access.yaml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/config/versions.yaml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/ARC-RUNNERS.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/CI-LESSONS.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/DESIGN.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/JFROG-MIGRATION.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/MIGRATION-GUIDE.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/PGO-WORKLOAD-GUIDE.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/deployment-contract.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/migration/deployment-contract-tier3.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/rust.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/typescript.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/pyproject.toml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/renovate.json +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/robots.txt +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/scripts/pre-commit-versions.sh +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/scripts/setup-rust-dev.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/scripts/sync-secrets-access.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/scripts/update-versions.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/__init__.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/common.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/config/native-deps/golang.yaml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/config/native-deps/python.yaml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/config/native-deps/rust.yaml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/config/native-deps/typescript.yaml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/config/org.yaml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/config/toolchains/gcc.yaml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/config/toolchains/llvm.yaml +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/container/__init__.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/container/binary_stage.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/container/build.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/container/compose.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/container/detect.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/container/labels.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/container/manifest.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/container/registry.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/container/stage.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/container/templates.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/deployment/__init__.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/deployment/cli.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/deployment/contract.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/deployment/detect.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/deployment/registry.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/deployment/scaffold.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/deployment/stage.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/detect.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/install_deps.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/__init__.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/_build_common.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/golang/__init__.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/golang/build.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/golang/publish.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/golang/quality.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/golang/test.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/python/__init__.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/python/build.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/python/publish.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/python/quality.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/python/test.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/quality_common.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/rust/__init__.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/rust/build.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/rust/optimize.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/rust/pgo.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/rust/publish.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/rust/quality.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/rust/test.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/typescript/__init__.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/typescript/_common.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/typescript/build.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/typescript/install_deps.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/typescript/publish.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/typescript/quality.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/typescript/test.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/logs.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/migrate.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/native_deps.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/publish/__init__.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/publish/binaries.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/publish/dispatch.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/publish_binaries.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/push.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/quality/__init__.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/quality/commit_validation.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/quality/gitleaks.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/release.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/trigger.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/upgrade.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/templates/pgo-workload/README.md +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/templates/pgo-workload/grpc-server.sh +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/templates/pgo-workload/http-server.sh +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/templates/pgo-workload/kafka-consumer.sh +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/templates/pgo-workload/kafka-producer.sh +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/templates/pgo-workload/multi-protocol.sh +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/__init__.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/integration/__init__.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/__init__.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/deployment/__init__.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/deployment/test_contract.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/deployment/test_detect_tier.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/deployment/test_emit_artefacts_cli.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/deployment/test_registry_cascade.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/deployment/test_scaffold.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/deployment/test_stage.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_cli.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_commit_validation.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_common.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_container_binary_stage.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_container_build.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_container_compose.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_container_detect.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_container_labels.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_container_manifest.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_container_registry.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_container_stage.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_container_templates.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_detect.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_dispatch_alias.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_gh.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_migrate.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_native_deps.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_publish.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_push.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_rust_optimize.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_rust_pgo.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_rust_quality.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_typescript_quality.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_upgrade.py +0 -0
- {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_workflow_consistency.py +0 -0
|
@@ -1,3 +1,20 @@
|
|
|
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
|
+
|
|
9
|
+
## [2.2.1](https://github.com/hyperi-io/hyperi-ci/compare/v2.2.0...v2.2.1) (2026-05-08)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* **init:** scaffold ci.yml with tag input on workflow_dispatch ([e6f7470](https://github.com/hyperi-io/hyperi-ci/commit/e6f7470c827ee8013db5ae2ed9b0af27c0226d93))
|
|
15
|
+
* **test:** align spike-channel allocator test with current jemalloc-everywhere policy ([9de19e4](https://github.com/hyperi-io/hyperi-ci/commit/9de19e4a3d897b44caf0c586f5548443c1b8015d))
|
|
16
|
+
* **watch:** accept --repo flag for cross-repo run watching ([378dea3](https://github.com/hyperi-io/hyperi-ci/commit/378dea37b86f7d3c3ca508233c57a9058ee28cdb))
|
|
17
|
+
|
|
1
18
|
# [2.2.0](https://github.com/hyperi-io/hyperi-ci/compare/v2.1.6...v2.2.0) (2026-05-08)
|
|
2
19
|
|
|
3
20
|
|
|
@@ -157,6 +157,16 @@ PR review and release-worthy pushes run them. The gate is computed
|
|
|
157
157
|
ONCE in the `plan` job and consumed by every downstream job; do not
|
|
158
158
|
re-implement the condition string elsewhere — `tests/unit/test_workflow_consistency.py` enforces this.
|
|
159
159
|
|
|
160
|
+
## CI bug-log convention (cross-repo)
|
|
161
|
+
|
|
162
|
+
CI bugs and fixes surfaced in consumer repos are logged under
|
|
163
|
+
`<consumer>/docs/superpowers/plans/<date>-ci-<topic>.md` (gitignored
|
|
164
|
+
local-only) plus a one-line entry in that consumer's `TODO.md`.
|
|
165
|
+
This is the SSoT location for CI fixes across the org — when canary
|
|
166
|
+
runs surface bugs, look there for the resolution status. The
|
|
167
|
+
hyperi-ci rollout doc references back to those plans so the loop
|
|
168
|
+
is closeable.
|
|
169
|
+
|
|
160
170
|
## Architecture
|
|
161
171
|
|
|
162
172
|
See `docs/DESIGN.md` for full architecture documentation.
|
|
@@ -332,6 +342,27 @@ See `docs/MIGRATION-GUIDE.md` for migrating projects from v1 to v2.
|
|
|
332
342
|
- **dfe-protocol-sdk** — plugin system removed
|
|
333
343
|
- **dfe-receiver-plugin-syslog** — syslog is built-in transport
|
|
334
344
|
|
|
345
|
+
## Future direction (aspirational)
|
|
346
|
+
|
|
347
|
+
Today: GitHub for git hosting, GitHub Actions for CI. Likely move when
|
|
348
|
+
budget and time allow:
|
|
349
|
+
|
|
350
|
+
- **Codeberg** for git hosting — reduce single-vendor lock-in to GitHub.
|
|
351
|
+
- **Buildkite** for CI — stronger pipeline ergonomics, self-hosted
|
|
352
|
+
runners without ARC's K8s overhead.
|
|
353
|
+
|
|
354
|
+
Design implications today:
|
|
355
|
+
|
|
356
|
+
- CI logic stays in the `hyperi-ci` Python CLI, not embedded in
|
|
357
|
+
workflow YAML. Buildkite (or any successor) calls the same CLI;
|
|
358
|
+
only the runner glue changes.
|
|
359
|
+
- Workflows stay thin — plan job + gates + handler dispatch.
|
|
360
|
+
- Avoid hard dependencies on GitHub-only features in handler code
|
|
361
|
+
(Actions-specific matrix syntax, GHCR-only auth flows).
|
|
362
|
+
|
|
363
|
+
Not on the near-term roadmap; recorded so we don't accidentally make
|
|
364
|
+
choices that paint us into the GitHub-Actions corner.
|
|
365
|
+
|
|
335
366
|
## Licensing
|
|
336
367
|
|
|
337
368
|
Proprietary — HYPERI PTY LIMITED.
|
|
@@ -6,33 +6,70 @@ 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
|
-
|
|
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.
|
|
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.**
|
|
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.
|
|
20
25
|
|
|
21
|
-
**
|
|
26
|
+
**Bugs discovered during rollout (no SEP fields):**
|
|
22
27
|
|
|
23
|
-
- [
|
|
24
|
-
- [
|
|
25
|
-
- [
|
|
28
|
+
- [x] `tests/integration/test_rust_build_optimize.py::test_spike_channel_default_uses_system_allocator` encoded the old "spike → system allocator" policy; current code (and standards/rules/RUST.md Allocator Policy) says jemalloc at every channel. Fixed in commit `a331a0b`.
|
|
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.
|
|
30
|
+
- [x] `ts-ci.yml` had a duplicate `setup:` job key (silent YAML override) and `test:` job missing `needs:`/`if:`. Fixed during Task 8.
|
|
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.
|
|
26
34
|
|
|
27
|
-
**
|
|
35
|
+
**Versions shipped:**
|
|
28
36
|
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
- A chore: commit on each canary completes in <2 min, skips quality/test/build/container/publish
|
|
32
|
-
- A `Publish: true` commit on each canary still runs the full pipeline and ships to the right registries
|
|
33
|
-
- v2.2.0+ of hyperi-ci is published to PyPI
|
|
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
|
|
34
39
|
|
|
35
|
-
**
|
|
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.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
### Follow-up: dedicated CI test repos (next plan)
|
|
45
|
+
|
|
46
|
+
**Why:** in-repo `test-projects/` fixtures validate the CLI, but cannot exercise GitHub-side workflow behaviour (chore-skip, semantic-release tagging, publish flow). Real validation needs separate repos that push commits and trigger real CI runs.
|
|
47
|
+
|
|
48
|
+
**Proposed minimum:**
|
|
49
|
+
|
|
50
|
+
- `hyperi-io/ci-test-rust-app` — single-crate binary (chore-skip, R2 + GH Release path)
|
|
51
|
+
- `hyperi-io/ci-test-rust-workspace` — 3-crate workspace (workspace version stamp)
|
|
52
|
+
- `hyperi-io/ci-test-python-pypi` — Python lib for PyPI (sdist clean)
|
|
53
|
+
- `hyperi-io/ci-test-python-app` — Python container app (Tier-3 deployment-contract path)
|
|
54
|
+
- `hyperi-io/ci-test-ts-app` — TypeScript bundle (npm publish)
|
|
55
|
+
- `hyperi-io/ci-test-go-app` — Go binary (cross-compile, GH Release)
|
|
56
|
+
|
|
57
|
+
**Status:** scoped only — needs its own plan written. Track separately when we pick this up.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
### Follow-up: deployment artefact distribution (next plan)
|
|
62
|
+
|
|
63
|
+
**Why:** `hyperi-ci run generate` produces `argocd-application.yaml`, `chart/` (Helm), `Dockerfile.runtime`, `container-manifest.json`. These get uploaded as a CI artifact and then... dropped on the floor. `_release-tail.yml` consumes only the container-manifest. ArgoCD operators have no consumable distribution path.
|
|
64
|
+
|
|
65
|
+
**Proposed:**
|
|
66
|
+
|
|
67
|
+
- Helm `chart/` → push to GHCR as OCI artifact (`oci://ghcr.io/hyperi-io/charts/<app>:vX.Y.Z`); ArgoCD ≥2.6 reads OCI natively
|
|
68
|
+
- `argocd-application.yaml` → PR into a `hyperi-io/gitops` cluster-config repo via GitHub App with `contents:write`
|
|
69
|
+
- Versioning: chart version === app version === `next-version` from plan output
|
|
70
|
+
- NOT R2 — Helm and ArgoCD don't read from R2; would need to reinvent chart-museum
|
|
71
|
+
|
|
72
|
+
**Status:** scoped only — needs its own plan written. Out of scope for the KISS consolidation.
|
|
36
73
|
|
|
37
74
|
---
|
|
38
75
|
|
hyperi_ci-2.2.2/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.2.2
|
|
@@ -154,6 +154,23 @@ workflows.
|
|
|
154
154
|
- ["GitHub Actions Is Slowly Killing Your Engineering Team"](https://www.iankduncan.com/engineering/2026-02-05-github-actions-killing-your-team/) — explicit warning against generic-abstraction-layer reusable workflows
|
|
155
155
|
- [Composite-action path resolution discussion #26245](https://github.com/orgs/community/discussions/26245) — confirms the cross-repo `./` problem is unsolved as of May 2026
|
|
156
156
|
|
|
157
|
+
## Future portability (aspirational)
|
|
158
|
+
|
|
159
|
+
We may move from GitHub + GitHub Actions to Codeberg (git) +
|
|
160
|
+
Buildkite (CI) when budget and time allow. Not on the near-term
|
|
161
|
+
roadmap, but it shapes design today:
|
|
162
|
+
|
|
163
|
+
- The `hyperi-ci` Python CLI owns the work — quality, test, build,
|
|
164
|
+
publish. The reusable workflows are thin runner glue around it.
|
|
165
|
+
Porting to Buildkite means rewriting the glue, not the CLI.
|
|
166
|
+
- Handler code (`src/hyperi_ci/languages/<lang>/*.py`) avoids
|
|
167
|
+
GitHub-only assumptions: no `${{ ... }}` template syntax leaking
|
|
168
|
+
into Python, no GHCR-only auth flows hardcoded in publish handlers.
|
|
169
|
+
- The plan job + gate-output pattern translates directly to
|
|
170
|
+
Buildkite's dynamic pipelines.
|
|
171
|
+
|
|
172
|
+
The shared CI tool keeps the cost of switching CI vendors bounded.
|
|
173
|
+
|
|
157
174
|
## Pre-2026-05-08 archaeology
|
|
158
175
|
|
|
159
176
|
Before this consolidation, `_setup.yml` existed as a shared first-job
|
|
@@ -424,11 +424,23 @@ def watch(
|
|
|
424
424
|
int,
|
|
425
425
|
typer.Option("--interval", "-i", help="Initial poll interval in seconds"),
|
|
426
426
|
] = 30,
|
|
427
|
+
repo: Annotated[
|
|
428
|
+
str | None,
|
|
429
|
+
typer.Option(
|
|
430
|
+
"--repo",
|
|
431
|
+
"-R",
|
|
432
|
+
help=(
|
|
433
|
+
"Target repo as owner/name (e.g. hyperi-io/dfe-loader). "
|
|
434
|
+
"Defaults to the cwd's git remote — set this when watching "
|
|
435
|
+
"a run in a different repo than your cwd."
|
|
436
|
+
),
|
|
437
|
+
),
|
|
438
|
+
] = None,
|
|
427
439
|
) -> None:
|
|
428
440
|
"""Watch a GitHub Actions run to completion."""
|
|
429
441
|
from hyperi_ci.watch import watch_run
|
|
430
442
|
|
|
431
|
-
rc = watch_run(run_id=run_id, timeout=timeout, interval=interval)
|
|
443
|
+
rc = watch_run(run_id=run_id, timeout=timeout, interval=interval, repo=repo)
|
|
432
444
|
raise typer.Exit(rc)
|
|
433
445
|
|
|
434
446
|
|
|
@@ -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)
|
|
@@ -91,18 +91,24 @@ def gh_json(
|
|
|
91
91
|
def get_latest_run(
|
|
92
92
|
branch: str | None = None,
|
|
93
93
|
workflow: str | None = None,
|
|
94
|
+
repo: str | None = None,
|
|
94
95
|
) -> dict | None:
|
|
95
96
|
"""Find the most recent workflow run.
|
|
96
97
|
|
|
97
98
|
Args:
|
|
98
99
|
branch: Filter by branch name.
|
|
99
100
|
workflow: Filter by workflow filename.
|
|
101
|
+
repo: Optional ``owner/name`` — when set, queries this repo
|
|
102
|
+
instead of the cwd's git remote. Use this when looking up
|
|
103
|
+
runs in a different repo than your cwd.
|
|
100
104
|
|
|
101
105
|
Returns:
|
|
102
106
|
Dict with run info, or None if no runs found.
|
|
103
107
|
|
|
104
108
|
"""
|
|
105
109
|
args = ["run", "list", "--limit", "1"]
|
|
110
|
+
if repo:
|
|
111
|
+
args.extend(["--repo", repo])
|
|
106
112
|
if branch:
|
|
107
113
|
args.extend(["--branch", branch])
|
|
108
114
|
if workflow:
|
|
@@ -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"]},
|
|
@@ -261,15 +267,22 @@ def _render_workflow(
|
|
|
261
267
|
" pull_request:\n"
|
|
262
268
|
" branches: [main]\n"
|
|
263
269
|
" workflow_dispatch:\n"
|
|
270
|
+
" inputs:\n"
|
|
271
|
+
" tag:\n"
|
|
272
|
+
" type: string\n"
|
|
273
|
+
" required: true\n"
|
|
274
|
+
' description: "Tag to publish (e.g. v1.3.0)"\n'
|
|
264
275
|
"\n"
|
|
265
276
|
"jobs:\n"
|
|
266
277
|
" ci:\n"
|
|
267
278
|
f" uses: {_CI_REPO}/.github/workflows/"
|
|
268
279
|
f"{workflow_file}@{_WORKFLOW_REF}\n"
|
|
280
|
+
" with:\n"
|
|
281
|
+
" tag: ${{ inputs.tag || '' }}\n"
|
|
269
282
|
)
|
|
270
283
|
|
|
271
284
|
if publish_target != "internal":
|
|
272
|
-
base += f"
|
|
285
|
+
base += f" publish-target: {publish_target}\n"
|
|
273
286
|
|
|
274
287
|
base += " secrets: inherit\n"
|
|
275
288
|
return base
|
|
@@ -67,11 +67,16 @@ def _poll_interval(base: int, attempt: int) -> float:
|
|
|
67
67
|
return min(base * (1.5 ** min(attempt - 1, 4)), 120.0)
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
def _get_run_status(run_id: str) -> dict | None:
|
|
70
|
+
def _get_run_status(run_id: str, repo: str | None = None) -> dict | None:
|
|
71
71
|
"""Fetch current run status.
|
|
72
72
|
|
|
73
73
|
Args:
|
|
74
74
|
run_id: Workflow run ID.
|
|
75
|
+
repo: Optional ``owner/name`` — pass this when watching a run
|
|
76
|
+
in a different repo than the current working directory.
|
|
77
|
+
``gh run view`` defaults to the cwd's git remote and
|
|
78
|
+
silently 404s when the run isn't there, which the watch
|
|
79
|
+
loop misreads as transient network failure.
|
|
75
80
|
|
|
76
81
|
Returns:
|
|
77
82
|
Dict with status/conclusion/jobs, or None on transient error.
|
|
@@ -82,26 +87,28 @@ def _get_run_status(run_id: str) -> dict | None:
|
|
|
82
87
|
`_MAX_CONSECUTIVE_FETCH_FAILURES`.
|
|
83
88
|
|
|
84
89
|
"""
|
|
90
|
+
args = [
|
|
91
|
+
"run",
|
|
92
|
+
"view",
|
|
93
|
+
run_id,
|
|
94
|
+
"--json",
|
|
95
|
+
"status,conclusion,jobs,url,workflowName,headBranch",
|
|
96
|
+
]
|
|
97
|
+
if repo:
|
|
98
|
+
args.extend(["--repo", repo])
|
|
85
99
|
try:
|
|
86
|
-
result = gh_run(
|
|
87
|
-
[
|
|
88
|
-
"run",
|
|
89
|
-
"view",
|
|
90
|
-
run_id,
|
|
91
|
-
"--json",
|
|
92
|
-
"status,conclusion,jobs,url,workflowName,headBranch",
|
|
93
|
-
]
|
|
94
|
-
)
|
|
100
|
+
result = gh_run(args)
|
|
95
101
|
return json.loads(result.stdout)
|
|
96
102
|
except (subprocess.CalledProcessError, json.JSONDecodeError):
|
|
97
103
|
return None
|
|
98
104
|
|
|
99
105
|
|
|
100
|
-
def _resume_command(run_id: str, timeout: int) -> str:
|
|
106
|
+
def _resume_command(run_id: str, timeout: int, repo: str | None = None) -> str:
|
|
101
107
|
"""Format a copy-pasteable resume command for the user."""
|
|
108
|
+
repo_arg = f" --repo {repo}" if repo else ""
|
|
102
109
|
if timeout == 0:
|
|
103
|
-
return f"hyperi-ci watch {run_id} --timeout 0"
|
|
104
|
-
return f"hyperi-ci watch {run_id} --timeout {timeout}"
|
|
110
|
+
return f"hyperi-ci watch {run_id}{repo_arg} --timeout 0"
|
|
111
|
+
return f"hyperi-ci watch {run_id}{repo_arg} --timeout {timeout}"
|
|
105
112
|
|
|
106
113
|
|
|
107
114
|
def _print_summary(run_data: dict) -> None:
|
|
@@ -146,6 +153,7 @@ def watch_run(
|
|
|
146
153
|
run_id: str | None = None,
|
|
147
154
|
timeout: int = _DEFAULT_TIMEOUT,
|
|
148
155
|
interval: int = 30,
|
|
156
|
+
repo: str | None = None,
|
|
149
157
|
) -> int:
|
|
150
158
|
"""Watch a GitHub Actions run to completion.
|
|
151
159
|
|
|
@@ -155,6 +163,9 @@ def watch_run(
|
|
|
155
163
|
(poll until the run reaches a terminal state). Default is
|
|
156
164
|
sized for Tier 2 Rust builds (3600 s = 60 min).
|
|
157
165
|
interval: Base poll interval in seconds.
|
|
166
|
+
repo: Optional ``owner/name`` — when set, all gh calls target
|
|
167
|
+
this repo instead of the cwd's git remote. Use this when
|
|
168
|
+
watching a run in a different repo than your cwd.
|
|
158
169
|
|
|
159
170
|
Returns:
|
|
160
171
|
Exit code: 0=success, 1=failed/cancelled/unreachable, 2=timeout.
|
|
@@ -170,16 +181,17 @@ def watch_run(
|
|
|
170
181
|
return 1
|
|
171
182
|
|
|
172
183
|
info(f"Finding latest run on {branch}...")
|
|
173
|
-
latest = get_latest_run(branch=branch)
|
|
184
|
+
latest = get_latest_run(branch=branch, repo=repo)
|
|
174
185
|
if not latest:
|
|
175
186
|
error(f"No runs found on {branch}")
|
|
176
187
|
return 1
|
|
177
188
|
run_id = str(latest["databaseId"])
|
|
178
189
|
|
|
190
|
+
repo_label = f" in {repo}" if repo else ""
|
|
179
191
|
if timeout == 0:
|
|
180
|
-
info(f"Watching run {run_id} (no timeout)")
|
|
192
|
+
info(f"Watching run {run_id}{repo_label} (no timeout)")
|
|
181
193
|
else:
|
|
182
|
-
info(f"Watching run {run_id} (timeout: {timeout}s)")
|
|
194
|
+
info(f"Watching run {run_id}{repo_label} (timeout: {timeout}s)")
|
|
183
195
|
|
|
184
196
|
# `deadline = None` disables the timeout check entirely.
|
|
185
197
|
deadline: float | None = None if timeout == 0 else time.monotonic() + timeout
|
|
@@ -192,7 +204,7 @@ def watch_run(
|
|
|
192
204
|
now = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
193
205
|
info(f" [{now}] polling (attempt {attempt})...")
|
|
194
206
|
|
|
195
|
-
run_data = _get_run_status(run_id)
|
|
207
|
+
run_data = _get_run_status(run_id, repo=repo)
|
|
196
208
|
if not run_data:
|
|
197
209
|
consecutive_failures += 1
|
|
198
210
|
if consecutive_failures >= _MAX_CONSECUTIVE_FETCH_FAILURES:
|
|
@@ -200,7 +212,7 @@ def watch_run(
|
|
|
200
212
|
f" Failed to fetch run status "
|
|
201
213
|
f"{consecutive_failures} times in a row — giving up. "
|
|
202
214
|
f"Last known status: {last_known_status}. "
|
|
203
|
-
f"Resume: {_resume_command(run_id, timeout)}"
|
|
215
|
+
f"Resume: {_resume_command(run_id, timeout, repo=repo)}"
|
|
204
216
|
)
|
|
205
217
|
return 1
|
|
206
218
|
warn(
|
|
@@ -232,7 +244,7 @@ def watch_run(
|
|
|
232
244
|
# in progress) or investigate (stuck / silently failing).
|
|
233
245
|
error(
|
|
234
246
|
f"Timeout after {timeout} seconds — run still {last_known_status}. "
|
|
235
|
-
f"Resume: {_resume_command(run_id, timeout)} "
|
|
247
|
+
f"Resume: {_resume_command(run_id, timeout, repo=repo)} "
|
|
236
248
|
f"(or use --timeout 0 to disable timeout)"
|
|
237
249
|
)
|
|
238
250
|
return 2
|
|
@@ -309,22 +309,25 @@ class TestChannelToBinaryFlow:
|
|
|
309
309
|
binary = tmp_path / "target" / "release" / "fixture-bin"
|
|
310
310
|
assert _nm_has_symbol(binary, "je_")
|
|
311
311
|
|
|
312
|
-
def
|
|
313
|
-
#
|
|
312
|
+
def test_spike_channel_default_uses_jemalloc(self, tmp_path) -> None:
|
|
313
|
+
# standards/rules/RUST.md "Allocator Policy" — DFE Rust binaries
|
|
314
|
+
# use jemalloc at EVERY channel for tooling consistency
|
|
315
|
+
# (jeprof works on every binary from day one). spike still gets
|
|
316
|
+
# thin LTO, but allocator is jemalloc.
|
|
314
317
|
_write_fixture_crate(
|
|
315
318
|
tmp_path,
|
|
316
|
-
with_jemalloc_feature=True,
|
|
319
|
+
with_jemalloc_feature=True,
|
|
317
320
|
wire_global_allocator=True,
|
|
318
321
|
)
|
|
319
322
|
|
|
320
323
|
profile = resolve_optimization_profile("spike", None)
|
|
321
|
-
assert profile.allocator == "
|
|
324
|
+
assert profile.allocator == "jemalloc"
|
|
322
325
|
|
|
323
326
|
result = _run_cargo_build(tmp_path, profile)
|
|
324
327
|
assert result.returncode == 0
|
|
325
328
|
|
|
326
329
|
binary = tmp_path / "target" / "release" / "fixture-bin"
|
|
327
|
-
assert
|
|
330
|
+
assert _nm_has_symbol(binary, "je_")
|
|
328
331
|
|
|
329
332
|
|
|
330
333
|
@pytest.mark.skipif(not CARGO_AVAILABLE, reason="cargo not installed")
|
|
@@ -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"
|