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.
Files changed (224) hide show
  1. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/CHANGELOG.md +17 -0
  2. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/PKG-INFO +1 -1
  3. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/STATE.md +31 -0
  4. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/TODO.md +53 -16
  5. hyperi_ci-2.2.2/VERSION +1 -0
  6. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/ARCHITECTURE.md +17 -0
  7. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/cli.py +13 -1
  8. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/config/defaults.yaml +21 -0
  9. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/config.py +29 -0
  10. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/dispatch.py +24 -1
  11. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/gh.py +6 -0
  12. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/init.py +14 -1
  13. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/watch.py +31 -19
  14. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/integration/test_rust_build_optimize.py +8 -5
  15. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_config.py +69 -1
  16. hyperi_ci-2.2.2/tests/unit/test_dispatch_status.py +96 -0
  17. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_init.py +17 -0
  18. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_watch.py +36 -0
  19. hyperi_ci-2.2.2/uv.lock +878 -0
  20. hyperi_ci-2.2.0/VERSION +0 -1
  21. hyperi_ci-2.2.0/uv.lock +0 -718
  22. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/.ai-version +0 -0
  23. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/commands/doco.md +0 -0
  24. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/commands/load.md +0 -0
  25. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/commands/review.md +0 -0
  26. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/commands/save.md +0 -0
  27. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/commands/setup-claude.md +0 -0
  28. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/commands/simplify.md +0 -0
  29. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/commands/standards.md +0 -0
  30. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/UNIVERSAL.md +0 -0
  31. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/ai-conduct.md +0 -0
  32. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/ansible.md +0 -0
  33. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/bash.md +0 -0
  34. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/chars-policy.md +0 -0
  35. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/ci.md +0 -0
  36. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/clickhouse-sql.md +0 -0
  37. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/code-header.md +0 -0
  38. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/code-style.md +0 -0
  39. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/config-and-logging.md +0 -0
  40. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/cpp.md +0 -0
  41. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/design-principles.md +0 -0
  42. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/dfe-metrics.md +0 -0
  43. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/docker.md +0 -0
  44. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/error-handling.md +0 -0
  45. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/git.md +0 -0
  46. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/golang.md +0 -0
  47. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/issue-management.md +0 -0
  48. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/k8s.md +0 -0
  49. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/licensing.md +0 -0
  50. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/mocks-policy.md +0 -0
  51. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/pki.md +0 -0
  52. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/python.md +0 -0
  53. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/repo-naming.md +0 -0
  54. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/rust.md +0 -0
  55. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/security.md +0 -0
  56. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/sql-clickhouse.md +0 -0
  57. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/terraform.md +0 -0
  58. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/testing.md +0 -0
  59. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/typescript.md +0 -0
  60. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/rules/universal.md +0 -0
  61. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/settings.json +0 -0
  62. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/ai-common/SKILL.md +0 -0
  63. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/ai-guidelines/SKILL.md +0 -0
  64. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/bleeding-edge/SKILL.md +0 -0
  65. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/ci-check/SKILL.md +0 -0
  66. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/ci-logs/SKILL.md +0 -0
  67. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/ci-watch/SKILL.md +0 -0
  68. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/deps/SKILL.md +0 -0
  69. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/docs-audit/SKILL.md +0 -0
  70. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/documentation/SKILL.md +0 -0
  71. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/python/SKILL.md +0 -0
  72. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/release/SKILL.md +0 -0
  73. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/standards/SKILL.md +0 -0
  74. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/test-review/SKILL.md +0 -0
  75. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.claude/skills/verification/SKILL.md +0 -0
  76. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.gitattributes +0 -0
  77. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.github/actions/predict-version/action.yml +0 -0
  78. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.github/workflows/_release-tail.yml +0 -0
  79. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.github/workflows/ci.yml +0 -0
  80. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.github/workflows/go-ci.yml +0 -0
  81. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.github/workflows/python-ci.yml +0 -0
  82. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.github/workflows/rust-ci.yml +0 -0
  83. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.github/workflows/ts-ci.yml +0 -0
  84. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.gitignore +0 -0
  85. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.gitmodules +0 -0
  86. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.hyperi-ci.yaml +0 -0
  87. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.mcp.json +0 -0
  88. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/.releaserc.yaml +0 -0
  89. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/AI-TRAINING-POLICY.md +0 -0
  90. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/CLAUDE.md +0 -0
  91. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/LICENSE +0 -0
  92. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/README.md +0 -0
  93. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/config/commit-types.yaml +0 -0
  94. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/config/org.yaml +0 -0
  95. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/config/runners.yaml +0 -0
  96. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/config/secrets-access.yaml +0 -0
  97. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/config/versions.yaml +0 -0
  98. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/ARC-RUNNERS.md +0 -0
  99. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/CI-LESSONS.md +0 -0
  100. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/DESIGN.md +0 -0
  101. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/JFROG-MIGRATION.md +0 -0
  102. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/MIGRATION-GUIDE.md +0 -0
  103. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/PGO-WORKLOAD-GUIDE.md +0 -0
  104. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/deployment-contract.md +0 -0
  105. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/migration/deployment-contract-tier3.md +0 -0
  106. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/rust.md +0 -0
  107. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/docs/typescript.md +0 -0
  108. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/pyproject.toml +0 -0
  109. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/renovate.json +0 -0
  110. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/robots.txt +0 -0
  111. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/scripts/pre-commit-versions.sh +0 -0
  112. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/scripts/setup-rust-dev.py +0 -0
  113. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/scripts/sync-secrets-access.py +0 -0
  114. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/scripts/update-versions.py +0 -0
  115. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/__init__.py +0 -0
  116. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/common.py +0 -0
  117. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/config/native-deps/golang.yaml +0 -0
  118. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/config/native-deps/python.yaml +0 -0
  119. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/config/native-deps/rust.yaml +0 -0
  120. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/config/native-deps/typescript.yaml +0 -0
  121. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/config/org.yaml +0 -0
  122. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/config/toolchains/gcc.yaml +0 -0
  123. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/config/toolchains/llvm.yaml +0 -0
  124. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/container/__init__.py +0 -0
  125. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/container/binary_stage.py +0 -0
  126. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/container/build.py +0 -0
  127. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/container/compose.py +0 -0
  128. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/container/detect.py +0 -0
  129. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/container/labels.py +0 -0
  130. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/container/manifest.py +0 -0
  131. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/container/registry.py +0 -0
  132. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/container/stage.py +0 -0
  133. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/container/templates.py +0 -0
  134. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/deployment/__init__.py +0 -0
  135. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/deployment/cli.py +0 -0
  136. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/deployment/contract.py +0 -0
  137. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/deployment/detect.py +0 -0
  138. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/deployment/registry.py +0 -0
  139. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/deployment/scaffold.py +0 -0
  140. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/deployment/stage.py +0 -0
  141. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/detect.py +0 -0
  142. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/install_deps.py +0 -0
  143. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/__init__.py +0 -0
  144. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/_build_common.py +0 -0
  145. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/golang/__init__.py +0 -0
  146. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/golang/build.py +0 -0
  147. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/golang/publish.py +0 -0
  148. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/golang/quality.py +0 -0
  149. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/golang/test.py +0 -0
  150. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/python/__init__.py +0 -0
  151. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/python/build.py +0 -0
  152. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/python/publish.py +0 -0
  153. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/python/quality.py +0 -0
  154. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/python/test.py +0 -0
  155. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/quality_common.py +0 -0
  156. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/rust/__init__.py +0 -0
  157. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/rust/build.py +0 -0
  158. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/rust/optimize.py +0 -0
  159. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/rust/pgo.py +0 -0
  160. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/rust/publish.py +0 -0
  161. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/rust/quality.py +0 -0
  162. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/rust/test.py +0 -0
  163. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/typescript/__init__.py +0 -0
  164. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/typescript/_common.py +0 -0
  165. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/typescript/build.py +0 -0
  166. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/typescript/install_deps.py +0 -0
  167. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/typescript/publish.py +0 -0
  168. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/typescript/quality.py +0 -0
  169. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/languages/typescript/test.py +0 -0
  170. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/logs.py +0 -0
  171. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/migrate.py +0 -0
  172. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/native_deps.py +0 -0
  173. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/publish/__init__.py +0 -0
  174. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/publish/binaries.py +0 -0
  175. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/publish/dispatch.py +0 -0
  176. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/publish_binaries.py +0 -0
  177. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/push.py +0 -0
  178. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/quality/__init__.py +0 -0
  179. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/quality/commit_validation.py +0 -0
  180. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/quality/gitleaks.py +0 -0
  181. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/release.py +0 -0
  182. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/trigger.py +0 -0
  183. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/src/hyperi_ci/upgrade.py +0 -0
  184. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/templates/pgo-workload/README.md +0 -0
  185. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/templates/pgo-workload/grpc-server.sh +0 -0
  186. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/templates/pgo-workload/http-server.sh +0 -0
  187. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/templates/pgo-workload/kafka-consumer.sh +0 -0
  188. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/templates/pgo-workload/kafka-producer.sh +0 -0
  189. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/templates/pgo-workload/multi-protocol.sh +0 -0
  190. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/__init__.py +0 -0
  191. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/integration/__init__.py +0 -0
  192. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/__init__.py +0 -0
  193. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/deployment/__init__.py +0 -0
  194. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/deployment/test_contract.py +0 -0
  195. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/deployment/test_detect_tier.py +0 -0
  196. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/deployment/test_emit_artefacts_cli.py +0 -0
  197. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/deployment/test_registry_cascade.py +0 -0
  198. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/deployment/test_scaffold.py +0 -0
  199. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/deployment/test_stage.py +0 -0
  200. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_cli.py +0 -0
  201. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_commit_validation.py +0 -0
  202. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_common.py +0 -0
  203. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_container_binary_stage.py +0 -0
  204. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_container_build.py +0 -0
  205. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_container_compose.py +0 -0
  206. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_container_detect.py +0 -0
  207. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_container_labels.py +0 -0
  208. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_container_manifest.py +0 -0
  209. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_container_registry.py +0 -0
  210. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_container_stage.py +0 -0
  211. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_container_templates.py +0 -0
  212. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_detect.py +0 -0
  213. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_dispatch_alias.py +0 -0
  214. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_gh.py +0 -0
  215. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_migrate.py +0 -0
  216. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_native_deps.py +0 -0
  217. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_publish.py +0 -0
  218. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_push.py +0 -0
  219. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_rust_optimize.py +0 -0
  220. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_rust_pgo.py +0 -0
  221. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_rust_quality.py +0 -0
  222. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_typescript_quality.py +0 -0
  223. {hyperi_ci-2.2.0 → hyperi_ci-2.2.2}/tests/unit/test_upgrade.py +0 -0
  224. {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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperi-ci
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: HyperI CI/CD CLI tool — multi-language build, test, and publish automation
5
5
  License: Proprietary
6
6
  License-File: LICENSE
@@ -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) (will be created in Task 1 of the plan)
13
+ **Architecture:** [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)
14
14
 
15
- **Why:** chore: commits + AI submodule bumps were burning ~25 minutes of CI compute per push. Two earlier consolidation attempts in this session (`_setup.yml` reusable workflow, sketched `_ci.yml` orchestrator) added indirection without solving the root cause. Web research (astral-sh/uv, tokio-rs/tokio, vercel/turborepo) showed the right shape: flat language workflow with a `plan` first job + conditional gates. No reusable-workflow chains.
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
- **Status:** plan written 2026-05-08. Implementation tracked task-by-task in the plan file (17 tasks).
17
+ **Canary results:**
18
18
 
19
- **Resume from this directory** (`/projects/hyperi-ci`) when picking this up the plan, STATE.md, TODO.md, and code all live here. Recommended execution: inline for Tasks 1-12 (mechanical YAML + tests), subagent-driven for Tasks 13-15 (canaries that need fresh per-project context).
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
- **Canaries (no SEP fields — fix bugs found, don't dismiss):**
26
+ **Bugs discovered during rollout (no SEP fields):**
22
27
 
23
- - [ ] **Canary 0: hyperi-ci's own `test-projects/`** `ci-test-python-app`, `ci-test-go-app`, `ci-test-ts-app` drives the iteration loop in-repo via `tests/integration/`.
24
- - [ ] **Canary 1: dfe-loader (Rust)** most complex Rust example: clickhouse-driver, Arrow, columnar deps. Must produce a tagged R2 + GHCR + GH Release artefact under the new shape, AND a chore: bump must skip build entirely.
25
- - [ ] **Canary 2: dfe-engine (Python)** most complex Python example. Same exit criteria.
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
- **Done means:**
35
+ **Versions shipped:**
28
36
 
29
- - All 17 plan tasks checked off
30
- - All four `<lang>-ci.yml` workflows match the same shape (verified by `tests/unit/test_workflow_consistency.py`)
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
- **Bugs discovered during rollout (no SEP fields):** populate as the rollout proceeds.
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
 
@@ -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" with:\n publish-target: {publish_target}\n"
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 test_spike_channel_default_uses_system_allocator(self, tmp_path) -> None:
313
- # spike channel should default to system allocator — no jemalloc.
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, # Available but not selected
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 == "system"
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 not _nm_has_symbol(binary, "je_malloc")
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 CIConfig, _merge_deep, _parse_env_value, load_config
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"