orbi-cli 0.6.0__tar.gz → 0.6.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 (154) hide show
  1. {orbi_cli-0.6.0/src/orbi_cli.egg-info → orbi_cli-0.6.2}/PKG-INFO +1 -1
  2. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/pyproject.toml +1 -1
  3. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/__init__.py +1 -1
  4. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/config.py +0 -19
  5. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/pi_session.py +18 -21
  6. {orbi_cli-0.6.0 → orbi_cli-0.6.2/src/orbi_cli.egg-info}/PKG-INFO +1 -1
  7. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_bootstrap_runner.py +45 -38
  8. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_docs_i18n.py +1 -1
  9. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_docs_labels.py +11 -1
  10. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_docs_site.py +1 -1
  11. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_pi_command.py +5 -4
  12. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_pi_extensions.py +22 -3
  13. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/LICENSE +0 -0
  14. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/README.md +0 -0
  15. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/docs/licenses/sustainable-use-license.md +0 -0
  16. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/setup.cfg +0 -0
  17. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/branch_reclaim.py +0 -0
  18. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/checks.py +0 -0
  19. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/claim.py +0 -0
  20. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/clarify.py +0 -0
  21. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/cli.py +0 -0
  22. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/cli_source.py +0 -0
  23. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/delivery_labels.py +0 -0
  24. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/delivery_scene.py +0 -0
  25. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/engine_source.py +0 -0
  26. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/example_config.toml +0 -0
  27. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/failure.py +0 -0
  28. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/failure_report.py +0 -0
  29. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/git_identity.py +0 -0
  30. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/git_transport.py +0 -0
  31. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/github.py +0 -0
  32. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/gitops.py +0 -0
  33. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/human_review.py +0 -0
  34. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/journal.py +0 -0
  35. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/launchd_deploy.py +0 -0
  36. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/merge_gate.py +0 -0
  37. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/merge_handoff.py +0 -0
  38. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/milestone.py +0 -0
  39. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/milestone_command.py +0 -0
  40. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/pi_activity.py +0 -0
  41. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/pi_command.py +0 -0
  42. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/pi_process.py +0 -0
  43. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/pi_recovery.py +0 -0
  44. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/pilot_setup.py +0 -0
  45. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/pilot_slots.py +0 -0
  46. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/progress.py +0 -0
  47. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/release.py +0 -0
  48. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/release_git.py +0 -0
  49. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/release_notes.py +0 -0
  50. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/repo_config.py +0 -0
  51. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/runner.py +0 -0
  52. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/runner_health.py +0 -0
  53. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/scene.py +0 -0
  54. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/scheduler.py +0 -0
  55. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/scheduler_units.py +0 -0
  56. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/source_pool.py +0 -0
  57. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/systemd_deploy.py +0 -0
  58. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi/ticket_command.py +0 -0
  59. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi_cli.egg-info/SOURCES.txt +0 -0
  60. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi_cli.egg-info/dependency_links.txt +0 -0
  61. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi_cli.egg-info/entry_points.txt +0 -0
  62. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/src/orbi_cli.egg-info/top_level.txt +0 -0
  63. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_agents_md.py +0 -0
  64. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_apply_label_patch.py +0 -0
  65. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_branch_reclaim.py +0 -0
  66. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_ci_failure_issue_workflow.py +0 -0
  67. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_ci_failure_triage.py +0 -0
  68. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_ci_macos_workflow.py +0 -0
  69. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_ci_workflow.py +0 -0
  70. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_clarify.py +0 -0
  71. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_claude_plugin.py +0 -0
  72. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_cli_install.py +0 -0
  73. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_cli_milestone.py +0 -0
  74. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_cli_packaging.py +0 -0
  75. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_cli_source.py +0 -0
  76. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_comment_readers_contract.py +0 -0
  77. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_concurrency_e2e.py +0 -0
  78. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_constitution.py +0 -0
  79. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_coverage_gate.py +0 -0
  80. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_deliver_pr_fakes.py +0 -0
  81. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_delivery_branch_delete.py +0 -0
  82. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_delivery_labels.py +0 -0
  83. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_delivery_scene.py +0 -0
  84. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_docker_docs.py +0 -0
  85. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_docker_entrypoint.py +0 -0
  86. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_docker_image_workflow.py +0 -0
  87. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_docs_issue_shapes.py +0 -0
  88. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_docs_mermaid.py +0 -0
  89. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_docs_releases.py +0 -0
  90. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_docs_single_source.py +0 -0
  91. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_engine_source.py +0 -0
  92. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_failure.py +0 -0
  93. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_failure_report.py +0 -0
  94. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_fix_needed_loop.py +0 -0
  95. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_gh_read_retry.py +0 -0
  96. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_git_base_smoke.py +0 -0
  97. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_git_merge_smoke.py +0 -0
  98. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_git_network_retry.py +0 -0
  99. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_git_transport.py +0 -0
  100. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_github.py +0 -0
  101. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_github_fakes.py +0 -0
  102. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_gitops.py +0 -0
  103. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_gitops_fakes.py +0 -0
  104. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_human_review.py +0 -0
  105. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_import_graph.py +0 -0
  106. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_install_sh.py +0 -0
  107. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_issue_1367.py +0 -0
  108. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_issue_461.py +0 -0
  109. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_issue_template_config.py +0 -0
  110. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_journal.py +0 -0
  111. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_journal_events.py +0 -0
  112. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_launchd_deploy.py +0 -0
  113. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_license.py +0 -0
  114. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_milestone_command.py +0 -0
  115. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_ops_e2e.py +0 -0
  116. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_orbi.py +0 -0
  117. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_orbi_dashboard.py +0 -0
  118. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_orbi_exporter.py +0 -0
  119. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_patch_ratchet.py +0 -0
  120. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_pi_activity.py +0 -0
  121. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_pi_provider_templates.py +0 -0
  122. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_pi_recovery.py +0 -0
  123. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_pi_session.py +0 -0
  124. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_pilot_setup.py +0 -0
  125. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_pilot_slots.py +0 -0
  126. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_pr_issue_link_workflow.py +0 -0
  127. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_prerequisite_check.py +0 -0
  128. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_progress.py +0 -0
  129. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_progress_wiring.py +0 -0
  130. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_prompt_contract.py +0 -0
  131. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_publish_workflow.py +0 -0
  132. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_release_docs_workflow.py +0 -0
  133. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_release_ticket_templates.py +0 -0
  134. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_repo_config.py +0 -0
  135. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_resume_continue_e2e.py +0 -0
  136. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_resume_e2e.py +0 -0
  137. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_resume_pr.py +0 -0
  138. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_review_merge.py +0 -0
  139. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_run_artifacts.py +0 -0
  140. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_run_id_e2e.py +0 -0
  141. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_runner_config.py +0 -0
  142. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_runner_health.py +0 -0
  143. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_runner_source_freshness.py +0 -0
  144. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_scene.py +0 -0
  145. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_scheduler.py +0 -0
  146. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_size_ratchet.py +0 -0
  147. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_src_layout.py +0 -0
  148. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_steering_review.py +0 -0
  149. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_suite_hygiene.py +0 -0
  150. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_systemd_deploy.py +0 -0
  151. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_systemd_timer.py +0 -0
  152. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_ticket_command.py +0 -0
  153. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_user_acceptance_contract.py +0 -0
  154. {orbi_cli-0.6.0 → orbi_cli-0.6.2}/tests/test_worktree_reclaim.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orbi-cli
3
- Version: 0.6.0
3
+ Version: 0.6.2
4
4
  Summary: Orbi — local AI development worker: claims GitHub Issues, runs Pi in isolated worktrees, and delivers PRs (GitHub Issues + labels are the only state store).
5
5
  Author: xqliu
6
6
  License-Expression: AGPL-3.0-only OR LicenseRef-Sustainable-Use-1.0
@@ -44,7 +44,7 @@ build-backend = "setuptools.build_meta"
44
44
 
45
45
  [project]
46
46
  name = "orbi-cli"
47
- version = "0.6.0"
47
+ version = "0.6.2"
48
48
  description = "Orbi — local AI development worker: claims GitHub Issues, runs Pi in isolated worktrees, and delivers PRs (GitHub Issues + labels are the only state store)."
49
49
  readme = "README.md"
50
50
  requires-python = ">=3.14"
@@ -18,4 +18,4 @@ the package would shadow the installed package for every process with
18
18
  the checkout root on sys.path. The direct-execution compatibility entry
19
19
  is `python3 -m orbi.cli` (development path only).
20
20
  """
21
- __version__ = "0.6.0"
21
+ __version__ = "0.6.2"
@@ -828,25 +828,6 @@ def _pi_provider_api_key_finding(path: Path, provider_id: str,
828
828
  return None
829
829
 
830
830
 
831
- def _expand_pi_api_key_refs(api_key: str) -> str:
832
- """Resolve `$VAR` / `${VAR}` references in an `apiKey`.
833
-
834
- Same reference syntax `_pi_provider_api_key_finding` validates (Pi's
835
- `docs/models.md`): every reference whose environment variable is
836
- set and non-empty is replaced by the real value; a reference whose
837
- variable is missing or empty — only possible for a non-selected
838
- provider, the selected one already failed config load otherwise —
839
- stays verbatim (that provider stays unavailable in Pi, the exact
840
- pre-#303 behavior). The value itself is never logged.
841
- """
842
- return re.sub(
843
- r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)",
844
- lambda match: os.environ.get(match.group(1) or match.group(2))
845
- or match.group(0),
846
- api_key,
847
- )
848
-
849
-
850
831
  def parse_repositories(entries: object, base: Path) -> list[dict]:
851
832
  """Parse the explicit multi-repo registry.
852
833
 
@@ -332,16 +332,17 @@ def prepare_pi_agent_dir(worktree: Path, config: config_domain.RunnerConfig,
332
332
  exists: stored auth for providers present in the merged catalog is
333
333
  still valid.
334
334
 
335
- `apiKey` env-var references (`$VAR` / `${VAR}`) are resolved into
336
- the per-run copy: config load already required the
337
- SELECTED provider's references to resolve, so the materialized
338
- catalog carries a usable real credential — without it Pi would
339
- hold the literal `$VAR` string and the request could never
340
- authenticate. References whose variable is missing or empty (only
341
- possible for non-selected providers) stay verbatim. The user's
342
- provider file and user agent dir are never modified, and the
343
- resolved key never reaches the journal, a comment, or a commit:
344
- the per-run dir is the gitignored `<worktree>/.orbi/pi-agent/`.
335
+ `apiKey` env-var references (`$VAR` / `${VAR}`) are copied
336
+ VERBATIM into the per-run catalog: Pi 1.0 interpolates them from
337
+ the process environment it inherits at request time (Pi
338
+ `docs/models.md`: "For an authenticated endpoint, `apiKey` and
339
+ header values can use `$NAME` or `${NAME}` environment
340
+ interpolation"), so the plaintext key is never written into the
341
+ worktree. Config load still requires the SELECTED provider's
342
+ references to resolve, so an unusable reference fails fast before
343
+ a run starts. The user's provider file and user agent dir are
344
+ never modified; the per-run dir is the gitignored
345
+ `<worktree>/.orbi/pi-agent/`.
345
346
  """
346
347
  providers_data = config.pi_providers_data
347
348
  if providers_data is None:
@@ -366,17 +367,13 @@ def prepare_pi_agent_dir(worktree: Path, config: config_domain.RunnerConfig,
366
367
  user_providers = {}
367
368
  merged_providers.update(user_providers)
368
369
  merged_providers.update(providers_data["providers"])
369
- # The per-run copy carries the resolved `apiKey` values
370
- # (entries with a string key are copied, so the loaded config data
371
- # keeps its literal references).
372
- resolved_providers: dict = {}
373
- for provider_id, entry in merged_providers.items():
374
- api_key = entry.get("apiKey") if isinstance(entry, dict) else None
375
- if isinstance(api_key, str) and api_key:
376
- entry = {**entry, "apiKey": config_domain._expand_pi_api_key_refs(api_key)}
377
- resolved_providers[provider_id] = entry
370
+ # The per-run catalog is written verbatim: an `apiKey` env-var
371
+ # reference stays a reference, so no plaintext credential is ever
372
+ # written into the worktree. Pi 1.0 interpolates `$NAME` /
373
+ # `${NAME}` from the process environment it inherits at request
374
+ # time (Pi `docs/models.md`).
378
375
  (agent_dir / "models.json").write_text(
379
- json.dumps({"providers": resolved_providers}, indent=2),
376
+ json.dumps({"providers": merged_providers}, indent=2),
380
377
  encoding="utf-8",
381
378
  )
382
379
  # Per-run settings.json: a real file consistent with
@@ -860,7 +857,7 @@ def run_ticket_agent(
860
857
  config, ROLE_TICKET, IMPLEMENT_EXCLUDED_SKILLS, session_dir,
861
858
  system_prompt, context,
862
859
  context_placeholder="<issue-context-redacted>",
863
- tools=False, extensions=False,
860
+ tools=False, extensions=True,
864
861
  )
865
862
  # Startup phase: the provider config is loaded and resolved
866
863
  # before the spawn; a configured provider file is materialized
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orbi-cli
3
- Version: 0.6.0
3
+ Version: 0.6.2
4
4
  Summary: Orbi — local AI development worker: claims GitHub Issues, runs Pi in isolated worktrees, and delivers PRs (GitHub Issues + labels are the only state store).
5
5
  Author: xqliu
6
6
  License-Expression: AGPL-3.0-only OR LicenseRef-Sustainable-Use-1.0
@@ -15010,43 +15010,48 @@ def test_prepare_pi_agent_dir_repo_providers_win_on_collision(
15010
15010
  )
15011
15011
 
15012
15012
 
15013
- def test_prepare_pi_agent_dir_expands_api_key_env_reference(
15013
+ def test_prepare_pi_agent_dir_keeps_api_key_env_reference_verbatim(
15014
15014
  tmp_path, monkeypatch,
15015
15015
  ):
15016
- """The per-run catalog carries the REAL key (Issue #303).
15016
+ """The per-run catalog keeps the literal `$VAR` reference (Issue #1554).
15017
15017
 
15018
- `_load_pi_providers` validates that the selected provider's
15019
- `$VAR` reference resolves; the materialized per-run `models.json`
15020
- must close the validate/use gap and write the resolved value,
15021
- otherwise the provider has no usable credential and the run
15022
- cannot authenticate. The loaded config data keeps the literal
15023
- reference (only the gitignored per-run copy materializes it).
15018
+ Pi 1.0 interpolates `apiKey` from the environment it inherits at
15019
+ request time, so the per-run `models.json` must never carry the
15020
+ plaintext key resolved into a file inside the worktree. Config load
15021
+ still validates that the selected provider's reference resolves.
15024
15022
  """
15025
15023
  home = tmp_path / "home"
15026
15024
  _user_agent_dir(home)
15027
15025
  monkeypatch.setenv("HOME", str(home))
15028
- monkeypatch.setenv("GROQ_API_KEY", "sk-real-key-value")
15029
- config = _model_config(
15030
- tmp_path, pi_providers_data=GROQ_PROVIDERS,
15031
- pi_provider="groq", pi_model="qwen/qwen3.8-27b",
15026
+ monkeypatch.setenv("ORBI_TEST_KEY", "secret-value")
15027
+ providers = {
15028
+ "providers": {
15029
+ "testprov": {
15030
+ "baseUrl": "http://127.0.0.1:18084/v1",
15031
+ "api": "openai-completions",
15032
+ "apiKey": "$ORBI_TEST_KEY",
15033
+ "models": [{"id": "test-model"}],
15034
+ },
15035
+ }
15036
+ }
15037
+ config_path = _providers_config(
15038
+ tmp_path, providers,
15039
+ pi_provider='"testprov"', pi_model='"test-model"',
15032
15040
  )
15041
+ config = config_domain.load_config(config_path)
15033
15042
  agent_dir = pi_session.prepare_pi_agent_dir(tmp_path, config)
15034
- merged = json.loads(
15035
- (agent_dir / "models.json").read_text(encoding="utf-8"),
15036
- )
15037
- assert merged["providers"]["groq"]["apiKey"] == "sk-real-key-value"
15038
- # The loaded provider data (and the user's file behind it) keeps
15039
- # the env-var reference: only the per-run copy materializes it.
15040
- assert config.pi_providers_data["providers"]["groq"][
15041
- "apiKey"
15042
- ] == "$GROQ_API_KEY"
15043
+ raw = (agent_dir / "models.json").read_text(encoding="utf-8")
15044
+ merged = json.loads(raw)
15045
+ assert merged["providers"]["testprov"]["apiKey"] == "$ORBI_TEST_KEY"
15046
+ assert "secret-value" not in raw
15043
15047
 
15044
15048
 
15045
- def test_prepare_pi_agent_dir_expands_braced_and_embedded_references(
15049
+ def test_prepare_pi_agent_dir_keeps_braced_and_embedded_references(
15046
15050
  tmp_path, monkeypatch,
15047
15051
  ):
15048
- """`${VAR}` and references embedded in larger literals expand too
15049
- (Pi's documented interpolation syntax, `docs/models.md`)."""
15052
+ """`${VAR}` and references embedded in larger literals stay verbatim
15053
+ too (Pi resolves its documented interpolation syntax at request
15054
+ time, `docs/models.md`)."""
15050
15055
  home = tmp_path / "home"
15051
15056
  _user_agent_dir(home)
15052
15057
  monkeypatch.setenv("HOME", str(home))
@@ -15070,15 +15075,17 @@ def test_prepare_pi_agent_dir_expands_braced_and_embedded_references(
15070
15075
  merged = json.loads(
15071
15076
  (agent_dir / "models.json").read_text(encoding="utf-8"),
15072
15077
  )
15073
- assert merged["providers"]["local"]["apiKey"] == "alpha_mid_omega"
15078
+ assert merged["providers"]["local"]["apiKey"] == (
15079
+ "${TEST_PREFIX}_mid_$TEST_SUFFIX"
15080
+ )
15074
15081
 
15075
15082
 
15076
- def test_prepare_pi_agent_dir_unresolved_reference_stays_verbatim(
15083
+ def test_prepare_pi_agent_dir_keeps_every_reference_verbatim(
15077
15084
  tmp_path, monkeypatch,
15078
15085
  ):
15079
- """Only the SELECTED provider's key must resolve: an unselected
15080
- provider whose variable is missing keeps the literal reference and
15081
- stays unavailable in Pi (the pre-#303 behavior, never an error)."""
15086
+ """Every provider's reference is copied verbatim, selected or not:
15087
+ a variable missing from the environment is resolved (or rejected)
15088
+ by Pi/config load, never materialized here."""
15082
15089
  home = tmp_path / "home"
15083
15090
  _user_agent_dir(home)
15084
15091
  monkeypatch.setenv("HOME", str(home))
@@ -15103,7 +15110,7 @@ def test_prepare_pi_agent_dir_unresolved_reference_stays_verbatim(
15103
15110
  merged = json.loads(
15104
15111
  (agent_dir / "models.json").read_text(encoding="utf-8"),
15105
15112
  )
15106
- assert merged["providers"]["groq"]["apiKey"] == "sk-real-key-value"
15113
+ assert merged["providers"]["groq"]["apiKey"] == "$GROQ_API_KEY"
15107
15114
  assert merged["providers"]["other"]["apiKey"] == "$MISSING_PROVIDER_KEY"
15108
15115
 
15109
15116
 
@@ -15112,7 +15119,7 @@ def test_prepare_pi_agent_dir_leaves_non_string_api_keys_untouched(
15112
15119
  ):
15113
15120
  """Entries without a string `apiKey` (absent, or a malformed
15114
15121
  non-object entry from the user's own models.json) pass through
15115
- unchanged — expansion never invents or crashes on them."""
15122
+ unchanged — the verbatim copy never invents or crashes on them."""
15116
15123
  home = tmp_path / "home"
15117
15124
  _user_agent_dir(
15118
15125
  home,
@@ -15134,12 +15141,12 @@ def test_prepare_pi_agent_dir_leaves_non_string_api_keys_untouched(
15134
15141
  assert merged["providers"]["junk"] == "not-an-object"
15135
15142
 
15136
15143
 
15137
- def test_prepare_pi_agent_dir_expands_zai_scene_from_load_config(
15144
+ def test_prepare_pi_agent_dir_keeps_zai_scene_reference_from_load_config(
15138
15145
  tmp_path, monkeypatch,
15139
15146
  ):
15140
- """The Issue #303 scene end to end: a z.ai-shaped provider file
15141
- (`apiKey: "$ZAI_API_KEY"`) loaded through `load_config` then
15142
- materialized must carry the real key in the per-run catalog."""
15147
+ """The z.ai-shaped provider file (`apiKey: "$ZAI_API_KEY"`) loaded
15148
+ through `load_config` must reach the per-run catalog verbatim, so
15149
+ the plaintext key is never written into the worktree (Issue #1554)."""
15143
15150
  home = tmp_path / "home"
15144
15151
  _user_agent_dir(home)
15145
15152
  monkeypatch.setenv("HOME", str(home))
@@ -15163,7 +15170,7 @@ def test_prepare_pi_agent_dir_expands_zai_scene_from_load_config(
15163
15170
  merged = json.loads(
15164
15171
  (agent_dir / "models.json").read_text(encoding="utf-8"),
15165
15172
  )
15166
- assert merged["providers"]["z-ai"]["apiKey"] == "sk-zai-real-key"
15173
+ assert merged["providers"]["z-ai"]["apiKey"] == "$ZAI_API_KEY"
15167
15174
  assert merged["providers"]["z-ai"]["baseUrl"] == (
15168
15175
  "https://api.z.ai/api/paas/v4"
15169
15176
  )
@@ -15708,8 +15715,8 @@ def test_load_config_api_key_from_deploy_env_file_ok(tmp_path, monkeypatch):
15708
15715
  )
15709
15716
  config = config_domain.load_config(config_path)
15710
15717
  assert config.pi_providers_data == GROQ_PROVIDERS
15711
- # The variable must be visible to the process so the per-run Pi agent
15712
- # dir expansion (_expand_pi_api_key_refs) resolves it too.
15718
+ # The variable must be visible to the process so Pi can interpolate
15719
+ # the verbatim `$GROQ_API_KEY` reference in the per-run catalog.
15713
15720
  assert os.environ.get("GROQ_API_KEY") == "file-key"
15714
15721
 
15715
15722
 
@@ -60,7 +60,7 @@ KNOWN_LABELS = frozenset({
60
60
  NEEDS_DETAIL_LABEL,
61
61
  })
62
62
 
63
- LABEL_PATTERN = re.compile(r"\bai-[a-z][a-z-]*\b")
63
+ LABEL_PATTERN = re.compile(r"(?<![\w-])ai-[a-z][a-z-]*\b")
64
64
 
65
65
  # Config fields the implementation understands (bootstrap_config_domain.load_config
66
66
  # plus the committed example) — the Chinese field table may document
@@ -46,7 +46,17 @@ KNOWN_LABELS = frozenset({
46
46
  NEEDS_DETAIL_LABEL,
47
47
  })
48
48
 
49
- LABEL_PATTERN = re.compile(r"\bai-[a-z][a-z-]*\b")
49
+ LABEL_PATTERN = re.compile(r"(?<![\w-])ai-[a-z][a-z-]*\b")
50
+
51
+
52
+ def test_label_pattern_ignores_hyphenated_names_containing_ai():
53
+ """Issue #1552: a hyphenated skill name like `write-ai-ready-issue`
54
+ contains the substring `ai-ready-issue`, which is not a label. Only a
55
+ standalone `ai-*` token preceded by a non-word/non-hyphen boundary
56
+ counts."""
57
+ assert LABEL_PATTERN.findall("the write-ai-ready-issue skill and ai-ready") == [
58
+ "ai-ready"
59
+ ]
50
60
 
51
61
 
52
62
  def test_docs_only_reference_existing_labels():
@@ -67,7 +67,7 @@ KNOWN_LABELS = frozenset({
67
67
  "ai-needs-detail",
68
68
  })
69
69
 
70
- LABEL_PATTERN = re.compile(r"\bai-[a-z][a-z-]*\b")
70
+ LABEL_PATTERN = re.compile(r"(?<![\w-])ai-[a-z][a-z-]*\b")
71
71
 
72
72
  # Config fields the implementation understands (bootstrap_config_domain.load_config
73
73
  # plus the committed example). A docs field table may document exactly this
@@ -63,17 +63,18 @@ def test_ticket_command_and_log_with_everything_configured():
63
63
  pi_command.IMPLEMENT_EXCLUDED_SKILLS, SESSION_DIR,
64
64
  SYSTEM_PROMPT, CONTEXT,
65
65
  context_placeholder="<issue-context-redacted>",
66
- tools=False, extensions=False,
66
+ tools=False, extensions=True,
67
67
  )
68
68
  assert command == [
69
- "pi", "--no-tools", *IMPLEMENT_SKILL_ARGS,
69
+ "pi", "--no-tools", *EXTENSION_ARGS, *IMPLEMENT_SKILL_ARGS,
70
70
  "--provider", "openai", "--model", "gpt-5.6-sol",
71
71
  "--thinking", "medium",
72
72
  "--print", "--session-dir", str(SESSION_DIR),
73
73
  "--system-prompt", SYSTEM_PROMPT, CONTEXT,
74
74
  ]
75
75
  assert log_command == [
76
- "pi", "--provider", "openai", "--model", "gpt-5.6-sol",
76
+ "pi", *EXTENSION_ARGS,
77
+ "--provider", "openai", "--model", "gpt-5.6-sol",
77
78
  "--thinking", "medium",
78
79
  "--print", "--session-dir", str(SESSION_DIR),
79
80
  "--system-prompt", "<redacted>", "<issue-context-redacted>",
@@ -131,7 +132,7 @@ def test_review_command_and_log_with_everything_configured():
131
132
  "placeholder", "expect_no_tools"),
132
133
  [
133
134
  (pi_command.ROLE_TICKET, pi_command.IMPLEMENT_EXCLUDED_SKILLS,
134
- False, False, "<issue-context-redacted>", True),
135
+ False, True, "<issue-context-redacted>", True),
135
136
  (ROLE_IMPLEMENT, pi_command.IMPLEMENT_EXCLUDED_SKILLS,
136
137
  True, True, "<issue-context-redacted>", False),
137
138
  (pi_command.ROLE_REVIEW, pi_command.REVIEW_EXCLUDED_SKILLS,
@@ -76,16 +76,35 @@ def test_pi_extension_args_and_env_isolate_disabled_and_secrets():
76
76
  assert "secret" not in " ".join(args)
77
77
 
78
78
 
79
- def test_run_pi_and_review_share_extension_contract(monkeypatch, tmp_path):
79
+ def test_run_pi_review_and_ticket_share_extension_contract(monkeypatch, tmp_path):
80
80
  (tmp_path / "prompt.md").write_text("system", encoding="utf-8")
81
81
  (tmp_path / "prompt_review.md").write_text("review", encoding="utf-8")
82
+ disabled = tmp_path / "disabled.mjs"
83
+ disabled.write_text("export default () => {}", encoding="utf-8")
82
84
  calls = []
83
85
  monkeypatch.setattr(pi_session, "stream_pi", lambda command, **kw: calls.append((command, kw)) or "ok")
84
- config = config_domain.RunnerConfig(prompt=tmp_path / "prompt.md", prompt_review=tmp_path / "prompt_review.md", repo_dir=tmp_path, source_repos=("owner/repo",), workspace_root=tmp_path, context_files=(), skills=(), base_branch="main", base_sha="abc", run_id="deadbeef", pi_extensions=({"source": "npm:fixture@1.2.3", "enabled": True, "env": {"FIXTURE_TOKEN": "secret"}},))
86
+ config = config_domain.RunnerConfig(prompt=tmp_path / "prompt.md", prompt_review=tmp_path / "prompt_review.md", repo_dir=tmp_path, source_repos=("owner/repo",), workspace_root=tmp_path, context_files=(), skills=(), base_branch="main", base_sha="abc", run_id="deadbeef", pi_extensions=({"source": "npm:fixture@1.2.3", "enabled": True, "env": {"FIXTURE_TOKEN": "secret"}}, {"source": str(disabled), "enabled": False, "env": {}}))
87
+ extension_args = pi_command._pi_extension_args(config)
88
+ assert extension_args == ["--no-extensions", "--extension", "npm:fixture@1.2.3"]
85
89
  pi_session.run_pi({"number": 1, "title": "t", "body": ""}, RunContext(run_id=config.run_id, issue={"number": 1, "title": "t", "body": ""}["number"], branch="b", worktree=tmp_path, source_repo="owner/repo"), config)
86
90
  pi_session.run_review(RunContext(run_id=config.run_id, issue=1, branch="b", worktree=tmp_path, source_repo="owner/repo"), {"number": 1, "url": "u", "base_oid": "b", "head_oid": "h", "head_ref": "r"}, config, 1)
87
91
  for command, kwargs in calls:
88
- assert command[1:4] == ["--no-extensions", "--extension", "npm:fixture@1.2.3"]
92
+ assert command[1:4] == extension_args
89
93
  assert kwargs["pi_env"] == {"FIXTURE_TOKEN": "secret"}
90
94
  assert "secret" not in " ".join(kwargs["log_command"])
91
95
  assert kwargs["log_command"][1:4] == command[1:4]
96
+ assert len(calls) == 2
97
+ # The ticket role (#1558) keeps the --no-tools boundary and passes the
98
+ # SAME isolated extension flags implement/review use.
99
+ pi_session.run_ticket_agent(
100
+ {"number": 1558, "title": "t", "body": ""}, config, "owner/repo",
101
+ )
102
+ command, kwargs = calls[2]
103
+ assert command[:2 + len(extension_args)] == [
104
+ "pi", "--no-tools", *extension_args,
105
+ ]
106
+ assert kwargs["log_command"][:1 + len(extension_args)] == [
107
+ "pi", *extension_args,
108
+ ]
109
+ assert kwargs["pi_env"] == {"FIXTURE_TOKEN": "secret"}
110
+ assert "secret" not in " ".join(kwargs["log_command"])
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes