claude-dev-env 8.12.0 → 8.14.0
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.
- package/.agents/skills/pull-request/scripts/test_pull_request.py +1 -1
- package/.agents/skills/team-advisor/SKILL.md +1 -1
- package/.agents/skills-archived/pr-converge/scripts/test_check_convergence.py +4 -4
- package/_shared/advisor/advisor-protocol.md +19 -19
- package/_shared/advisor/reference/advisor-block.md +1 -1
- package/_shared/advisor/reference/astra-rung.md +11 -11
- package/_shared/advisor/reference/identity.md +4 -4
- package/_shared/advisor/reference/lifecycle.md +2 -2
- package/_shared/advisor/reference/spawn-walk-log.md +4 -4
- package/_shared/advisor/reference/third-party-bind.md +7 -7
- package/_shared/advisor/reference/warm-up.md +3 -3
- package/_shared/advisor/scripts/codex_astra_advisor.py +33 -26
- package/_shared/advisor/scripts/codex_astra_preflight.py +57 -55
- package/_shared/advisor/scripts/config/advisor_scripts_constants/advisor_route_constants.py +2 -2
- package/_shared/advisor/scripts/config/advisor_scripts_constants/astra_advisor_constants.py +8 -8
- package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +3 -3
- package/_shared/advisor/scripts/model_tier_run_validator.py +8 -9
- package/_shared/advisor/scripts/tests/test_codex_astra_advisor.py +69 -23
- package/_shared/advisor/scripts/tests/test_codex_astra_preflight.py +4 -4
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +91 -91
- package/_shared/pr-loop/scripts/check_convergence.py +54 -49
- package/_shared/pr-loop/scripts/codex_review_scripts_constants/run_constants.py +4 -0
- package/_shared/pr-loop/scripts/pr_converge_scripts_constants/convergence_gate_constants.py +10 -1
- package/_shared/pr-loop/scripts/run_codex_review.py +4 -6
- package/_shared/pr-loop/scripts/test_check_convergence.py +6 -6
- package/_shared/pr-loop/scripts/test_check_convergence_codex.py +73 -31
- package/_shared/pr-loop/scripts/test_check_convergence_contract.py +2 -2
- package/_shared/pr-loop/scripts/tests/test__claude_permissions_common.py +2 -2
- package/bin/git_hooks_installer.test.mjs +1 -1
- package/bin/install-constants.mjs +1 -6
- package/bin/install.cursor-rules.test.mjs +24 -51
- package/bin/install.mjs +16 -70
- package/docs/codex-accounts.md +79 -0
- package/hooks/blocking/test_duplicate_rmtree_helper_blocker.py +5 -5
- package/hooks/blocking/test_pii_scanner.py +1 -1
- package/hooks/blocking/test_pytest_testpaths_orphan_blocker.py +2 -2
- package/hooks/git-hooks/conftest.py +16 -0
- package/hooks/git-hooks/git_hooks_constants/verification_notice_constants.py +1 -1
- package/hooks/git-hooks/test_post_commit.py +1 -1
- package/hooks/git-hooks/test_pre_commit.py +1 -1
- package/hooks/git-hooks/test_pre_push.py +2 -2
- package/hooks/git-hooks/test_verification_start.py +1 -1
- package/hooks/git-hooks/verification_notice.py +2 -2
- package/hooks/git-hooks/verification_notice_context.py +21 -2
- package/hooks/git-hooks/verification_start.py +2 -3
- package/package.json +1 -1
- package/rules/durable-post-artifacts.md +4 -3
- package/scripts/claude_account_profile.py +21 -9
- package/scripts/codex_account_choice.py +396 -0
- package/scripts/codex_account_meters.py +372 -0
- package/scripts/dev_env_scripts_constants/codex_account_constants.py +141 -0
- package/scripts/dev_env_scripts_constants/private_term_constants.py +17 -2
- package/scripts/private_term_scan.py +6 -2
- package/scripts/repository_checks/config/constants.py +1 -0
- package/scripts/repository_checks/tracked_private_terms.py +7 -0
- package/scripts/test_claude_account_profile.py +12 -0
- package/scripts/test_codex_account_choice.py +276 -0
- package/scripts/test_codex_account_meters.py +205 -0
- package/scripts/tests/test_automatic_advisory.py +3 -3
- package/scripts/tests/test_automatic_advisory_polling.py +1 -1
- package/scripts/tests/test_automatic_advisory_recovery.py +1 -1
- package/scripts/tests/test_closed_pr_label.py +1 -1
- package/scripts/tests/test_private_term_scan.py +28 -0
- package/scripts/tests/test_review_closure.py +2 -2
- package/scripts/tests/test_review_closure_model.py +2 -2
- package/scripts/tests/test_rules.py +2 -83
- package/scripts/tests/test_tracked_private_terms.py +15 -0
- package/scripts/tests/test_verification_notice.py +29 -13
- package/.agents/skills/cert-classification-rule/SKILL.md +0 -224
- package/.agents/skills/cert-classification-rule/scripts/resort_cert_folders.py +0 -158
- package/_shared/pr-loop/scripts/codex_review_scripts_constants/codex_usage_probe_constants.py +0 -82
- package/_shared/pr-loop/scripts/codex_usage_probe.py +0 -573
- package/_shared/pr-loop/scripts/test_codex_usage_probe.py +0 -683
- package/scripts/sync_to_cursor/AGENTS.md +0 -1
- package/scripts/sync_to_cursor/CLAUDE.md +0 -1
- package/scripts/sync_to_cursor/__init__.py +0 -13
- package/scripts/sync_to_cursor/canonical_docs.py +0 -66
- package/scripts/sync_to_cursor/config/__init__.py +0 -8
- package/scripts/sync_to_cursor/engine.py +0 -219
- package/scripts/sync_to_cursor/hashing.py +0 -7
- package/scripts/sync_to_cursor/paths.py +0 -18
- package/scripts/sync_to_cursor/rules.py +0 -494
- package/scripts/sync_to_cursor.py +0 -22
- package/scripts/tests/test_engine.py +0 -101
- package/scripts/tests/test_sync_to_cursor.py +0 -315
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Codex accounts
|
|
2
|
+
|
|
3
|
+
The codex_account_choice picker spreads agent work across up to four Codex
|
|
4
|
+
accounts on one machine. Each account signs in under
|
|
5
|
+
its own Codex home, and every home shares the same Codex setup: config, rules,
|
|
6
|
+
skills, plugins and prompts. A job asks the picker which account to use.
|
|
7
|
+
|
|
8
|
+
## Pieces
|
|
9
|
+
|
|
10
|
+
| File | What it does |
|
|
11
|
+
|---|---|
|
|
12
|
+
| `scripts/codex_account_choice.py` | `choose` names the account and tier a job runs on, `check` tells a running job whether its account is still above a floor, `sync` links the shared setup into every account's home |
|
|
13
|
+
| `scripts/codex_account_meters.py` | Reads one account's rate-limit windows through `codex app-server` with `CODEX_HOME` set to that account's home |
|
|
14
|
+
| `scripts/dev_env_scripts_constants/codex_account_constants.py` | The account names in try order, the shared entry names, the 10% bar, the 1% Luna stop, and the 20% 5-hour floor for Luna |
|
|
15
|
+
|
|
16
|
+
## Names and order
|
|
17
|
+
|
|
18
|
+
The accounts are `codex-1`, `codex-2`, `codex-3` and `codex-4`. Their homes are
|
|
19
|
+
`~/.codex-profiles/codex-1` and so on, or under `CODEX_PROFILES_ROOT` when set.
|
|
20
|
+
Jobs try them in that order. The names say nothing about a plan, so a plan change
|
|
21
|
+
keeps every name. To change the order, sign the accounts into different folders.
|
|
22
|
+
|
|
23
|
+
## Sign in once
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
python packages/claude-dev-env/scripts/codex_account_choice.py sync
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Then, for each account, sign in with that account's home:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
$env:CODEX_HOME = "$HOME\.codex-profiles\codex-1"; codex login
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The sign-in lives in that folder's `auth.json`. Only the entries in
|
|
36
|
+
`ALL_SHARED_CODEX_HOME_NAMES` link to `~/.codex`. Sign-in, sessions, history,
|
|
37
|
+
logs and state files stay per account.
|
|
38
|
+
|
|
39
|
+
## Which account a job uses
|
|
40
|
+
|
|
41
|
+
Room is the smaller of an account's two windows: the 5-hour window and the week.
|
|
42
|
+
|
|
43
|
+
| Condition | Answer |
|
|
44
|
+
|---|---|
|
|
45
|
+
| First account in order with more than 10% left | `normal` on that account |
|
|
46
|
+
| No account over 10%, one or more over 1% | `luna` on the account with the most room, `stop_below_percent` 1 |
|
|
47
|
+
| An account that reports a 5-hour window, with under 20% of that window left | never takes `luna` |
|
|
48
|
+
| No account over 1% | `wait`, naming the account whose blocking windows reset first |
|
|
49
|
+
| Account not signed in, or its meter unread | skipped, with the reason in `accounts` |
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
python packages/claude-dev-env/scripts/codex_account_choice.py choose
|
|
53
|
+
{"tier": "normal", "account": "codex-1", "codex_home": "...\\codex-1", "percent_left": 62.0,
|
|
54
|
+
"stop_below_percent": null, "reason": "codex-1 has 62% left", "accounts": [...]}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
A job runs Codex with `CODEX_HOME` set to `codex_home`. On `luna`, the job runs
|
|
58
|
+
Luna and polls `check` between steps:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
python packages/claude-dev-env/scripts/codex_account_choice.py check codex-2 --floor 1
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`check` exits 0 while the account has more than the floor left, and 3 once it has
|
|
65
|
+
not or its meter is unread. The job stops on 3.
|
|
66
|
+
|
|
67
|
+
## Shared callers
|
|
68
|
+
|
|
69
|
+
Two scripts under `_shared` run `choose` by path. Each finds the picker at
|
|
70
|
+
`scripts/codex_account_choice.py` in the directory that holds `_shared`. Once
|
|
71
|
+
installed, that is `~/.claude/scripts/codex_account_choice.py`.
|
|
72
|
+
|
|
73
|
+
| Caller | What it does with the answer |
|
|
74
|
+
|---|---|
|
|
75
|
+
| `_shared/pr-loop/scripts/check_convergence.py` | Requires a Codex clean stamp on HEAD only on `normal`. `luna`, `wait`, or a failed picker skips the Codex gate |
|
|
76
|
+
| `_shared/advisor/scripts/codex_astra_advisor.py` | Binds Astra only on `normal`, and runs Codex with `CODEX_HOME` set to `codex_home` |
|
|
77
|
+
|
|
78
|
+
Pass `--codex-path` when `codex` is off PATH. Without it the picker also tries
|
|
79
|
+
the desktop install path under the user home.
|
|
@@ -172,7 +172,7 @@ def test_path_exempts_test_file_suffix() -> None:
|
|
|
172
172
|
|
|
173
173
|
def test_path_does_not_exempt_production_module() -> None:
|
|
174
174
|
assert not path_is_exempt(
|
|
175
|
-
"shared_utils/
|
|
175
|
+
"shared_utils/vendor_utils/cert_failure_processor/failure_categorizer.py"
|
|
176
176
|
)
|
|
177
177
|
|
|
178
178
|
|
|
@@ -241,7 +241,7 @@ def test_main_blocks_trio_copy_introduced_by_second_multi_edit() -> None:
|
|
|
241
241
|
{
|
|
242
242
|
"tool_name": "MultiEdit",
|
|
243
243
|
"tool_input": {
|
|
244
|
-
"file_path": "shared_utils/
|
|
244
|
+
"file_path": "shared_utils/vendor_utils/cert_failure_processor/parser.py",
|
|
245
245
|
"edits": [
|
|
246
246
|
{"old_string": "a = 1", "new_string": "a = 11"},
|
|
247
247
|
{"old_string": "b = 2", "new_string": COPIED_TRIO},
|
|
@@ -261,7 +261,7 @@ def test_main_allows_clean_multi_edit() -> None:
|
|
|
261
261
|
{
|
|
262
262
|
"tool_name": "MultiEdit",
|
|
263
263
|
"tool_input": {
|
|
264
|
-
"file_path": "shared_utils/
|
|
264
|
+
"file_path": "shared_utils/vendor_utils/cert_failure_processor/parser.py",
|
|
265
265
|
"edits": [
|
|
266
266
|
{"old_string": "a = 1", "new_string": "a = 11"},
|
|
267
267
|
{"old_string": "b = 2", "new_string": "b = 22"},
|
|
@@ -301,7 +301,7 @@ def test_main_blocks_local_trio_copy_in_production_module() -> None:
|
|
|
301
301
|
"tool_name": "Write",
|
|
302
302
|
"tool_input": {
|
|
303
303
|
"file_path": (
|
|
304
|
-
"shared_utils/
|
|
304
|
+
"shared_utils/vendor_utils/cert_failure_processor/failure_categorizer.py"
|
|
305
305
|
),
|
|
306
306
|
"content": COPIED_TRIO,
|
|
307
307
|
},
|
|
@@ -319,7 +319,7 @@ def test_main_allows_import_of_shared_helper() -> None:
|
|
|
319
319
|
{
|
|
320
320
|
"tool_name": "Write",
|
|
321
321
|
"tool_input": {
|
|
322
|
-
"file_path": "shared_utils/
|
|
322
|
+
"file_path": "shared_utils/vendor_utils/cleanup.py",
|
|
323
323
|
"content": (
|
|
324
324
|
"from shared_utils.web_automation.utils.windows_filesystem import "
|
|
325
325
|
"force_rmtree\n\nforce_rmtree(path)\n"
|
|
@@ -24,7 +24,7 @@ SYNTHETIC_AWS_KEY = "AKIA" + ("B" * 16)
|
|
|
24
24
|
SYNTHETIC_PEM_HEADER = "-----BEGIN RSA PRIVATE KEY-----"
|
|
25
25
|
SYNTHETIC_REAL_EMAIL = "person.fixture@company-example.io"
|
|
26
26
|
SYNTHETIC_SAFE_EMAIL = "user@example.com"
|
|
27
|
-
SYNTHETIC_GITHUB_NOREPLY_EMAIL = "
|
|
27
|
+
SYNTHETIC_GITHUB_NOREPLY_EMAIL = "81234567+octocat@users.noreply.github.com"
|
|
28
28
|
SYNTHETIC_HOME_PATH = r"C:\Users\fixture_real_user\notes.txt"
|
|
29
29
|
SYNTHETIC_PLACEHOLDER_HOME = r"C:\Users\<you>\notes.txt"
|
|
30
30
|
SYNTHETIC_PRIVATE_IP = "192.168.42.17"
|
|
@@ -20,7 +20,7 @@ HOOK_SCRIPT_PATH = HOOK_DIRECTORY / "pytest_testpaths_orphan_blocker.py"
|
|
|
20
20
|
|
|
21
21
|
PYPROJECT_WITH_EXPLICIT_TESTPATHS = (
|
|
22
22
|
"[tool.pytest.ini_options]\n"
|
|
23
|
-
'testpaths = [\n "tests",\n "
|
|
23
|
+
'testpaths = [\n "tests",\n "vendor_utils/tests",\n]\n'
|
|
24
24
|
'python_files = ["test_*.py"]\n'
|
|
25
25
|
)
|
|
26
26
|
|
|
@@ -91,7 +91,7 @@ def test_find_flags_test_directory_absent_from_explicit_testpaths(tmp_path: Path
|
|
|
91
91
|
def test_find_passes_test_directory_listed_in_testpaths(tmp_path: Path) -> None:
|
|
92
92
|
package_root = tmp_path / "shared_utils"
|
|
93
93
|
_write_package(package_root, PYPROJECT_WITH_EXPLICIT_TESTPATHS)
|
|
94
|
-
registered_test_file = package_root / "
|
|
94
|
+
registered_test_file = package_root / "vendor_utils" / "tests" / "test_normalizer.py"
|
|
95
95
|
assert find_unregistered_test_directory(str(registered_test_file)) is None
|
|
96
96
|
|
|
97
97
|
|
|
@@ -28,3 +28,19 @@ for each_module_name in list(sys.modules):
|
|
|
28
28
|
):
|
|
29
29
|
del sys.modules[each_module_name]
|
|
30
30
|
importlib.invalidate_caches()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
import pytest
|
|
34
|
+
|
|
35
|
+
from git_hooks_constants.verification_notice_constants import (
|
|
36
|
+
TARGET_REPOSITORY_ENVIRONMENT_VARIABLE,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
EXAMPLE_TARGET_REPOSITORY = "example-owner/example-repo"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.fixture(autouse=True)
|
|
43
|
+
def configured_target_repository(monkeypatch: pytest.MonkeyPatch) -> str:
|
|
44
|
+
"""Point the verification notice at the example repository for each test."""
|
|
45
|
+
monkeypatch.setenv(TARGET_REPOSITORY_ENVIRONMENT_VARIABLE, EXAMPLE_TARGET_REPOSITORY)
|
|
46
|
+
return EXAMPLE_TARGET_REPOSITORY
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
NOTICE_EVENT_COMMIT: str = "commit"
|
|
4
4
|
NOTICE_EVENT_PUSH: str = "push"
|
|
5
5
|
ALL_NOTICE_EVENTS: frozenset[str] = frozenset({NOTICE_EVENT_COMMIT, NOTICE_EVENT_PUSH})
|
|
6
|
-
|
|
6
|
+
TARGET_REPOSITORY_ENVIRONMENT_VARIABLE: str = "CLAUDE_VERIFICATION_TARGET_REPOSITORY"
|
|
7
7
|
GIT_CONFIG_SUBCOMMAND: str = "config"
|
|
8
8
|
GIT_REV_PARSE_SUBCOMMAND: str = "rev-parse"
|
|
9
9
|
ALL_GIT_REMOTE_URL_QUERY: tuple[str, ...] = (GIT_CONFIG_SUBCOMMAND, "--get", "remote.origin.url")
|
|
@@ -27,7 +27,7 @@ def test_native_git_commit_notice_reports_the_new_head(tmp_path: Path) -> None:
|
|
|
27
27
|
repository_path = create_native_repository(
|
|
28
28
|
tmp_path,
|
|
29
29
|
"repo",
|
|
30
|
-
"https://github.com/
|
|
30
|
+
"https://github.com/example-owner/example-repo.git",
|
|
31
31
|
)
|
|
32
32
|
hook_directory = tmp_path / "hooks"
|
|
33
33
|
install_native_hook(repository_path, hook_directory, "post-commit", "post_commit")
|
|
@@ -44,7 +44,7 @@ def test_native_git_commit_allows_missing_setup_and_prints_advisory(tmp_path: Pa
|
|
|
44
44
|
repository_path.mkdir()
|
|
45
45
|
configure_native_repository(
|
|
46
46
|
repository_path,
|
|
47
|
-
"https://github.com/
|
|
47
|
+
"https://github.com/example-owner/example-repo.git",
|
|
48
48
|
)
|
|
49
49
|
hook_directory = tmp_path / "hooks"
|
|
50
50
|
install_native_hook(repository_path, hook_directory, "pre-commit", "pre_commit")
|
|
@@ -44,7 +44,7 @@ def test_native_git_push_to_local_bare_remote_allows_missing_setup(tmp_path: Pat
|
|
|
44
44
|
repository_path = create_native_repository(
|
|
45
45
|
tmp_path,
|
|
46
46
|
"repo",
|
|
47
|
-
"https://github.com/
|
|
47
|
+
"https://github.com/example-owner/example-repo.git",
|
|
48
48
|
initial_branch="main",
|
|
49
49
|
)
|
|
50
50
|
run_git(repository_path, "add", "README.md")
|
|
@@ -54,7 +54,7 @@ def test_native_git_push_to_local_bare_remote_allows_missing_setup(tmp_path: Pat
|
|
|
54
54
|
configure_remote_url_rewrite(
|
|
55
55
|
repository_path,
|
|
56
56
|
bare_repository_path,
|
|
57
|
-
"https://github.com/
|
|
57
|
+
"https://github.com/example-owner/example-repo.git",
|
|
58
58
|
)
|
|
59
59
|
hook_directory = tmp_path / "hooks"
|
|
60
60
|
install_native_hook(repository_path, hook_directory, "pre-push", "pre_push")
|
|
@@ -23,7 +23,7 @@ def build_context(
|
|
|
23
23
|
repository_root: Path,
|
|
24
24
|
*,
|
|
25
25
|
event: str = "commit",
|
|
26
|
-
repository_remote: str = "
|
|
26
|
+
repository_remote: str = "example-owner/example-repo",
|
|
27
27
|
) -> VerificationNoticeContext:
|
|
28
28
|
git_directory = repository_root / ".git"
|
|
29
29
|
return VerificationNoticeContext(
|
|
@@ -35,7 +35,6 @@ from git_hooks_constants.verification_notice_constants import (
|
|
|
35
35
|
PYTHON_COMMAND,
|
|
36
36
|
REPORT_FILE_NAME,
|
|
37
37
|
SCOPED_VERIFICATION_SCRIPT_PATH,
|
|
38
|
-
TARGET_REPOSITORY_REMOTE,
|
|
39
38
|
UNKNOWN_SHA,
|
|
40
39
|
)
|
|
41
40
|
|
|
@@ -49,6 +48,7 @@ from verification_notice_context import (
|
|
|
49
48
|
VerificationNoticeContext,
|
|
50
49
|
_load_notice_context,
|
|
51
50
|
_run_git_query,
|
|
51
|
+
is_target_repository,
|
|
52
52
|
)
|
|
53
53
|
from verification_start import load_runner_configuration, start_automatic_advisory
|
|
54
54
|
from verification_notice_state import (
|
|
@@ -71,7 +71,7 @@ def build_verification_notice(context: VerificationNoticeContext) -> str:
|
|
|
71
71
|
Returns:
|
|
72
72
|
The formatted advisory text, or an empty string for another repository.
|
|
73
73
|
"""
|
|
74
|
-
if context.repository_remote
|
|
74
|
+
if not is_target_repository(context.repository_remote):
|
|
75
75
|
return ""
|
|
76
76
|
current_head = context.current_head or UNKNOWN_SHA
|
|
77
77
|
state, verified_head = _evaluate_context(context, _run_git_query)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
+
import os
|
|
6
7
|
import subprocess
|
|
7
8
|
from collections.abc import Mapping
|
|
8
9
|
from dataclasses import dataclass
|
|
@@ -32,7 +33,7 @@ from git_hooks_constants.verification_notice_constants import (
|
|
|
32
33
|
REPOSITORY_PART_INDEX,
|
|
33
34
|
SELECTED_MANIFEST_PATH_FIELD,
|
|
34
35
|
SELECTION_FIELD,
|
|
35
|
-
|
|
36
|
+
TARGET_REPOSITORY_ENVIRONMENT_VARIABLE,
|
|
36
37
|
)
|
|
37
38
|
from local_verification.manifest import ManifestRunFatal, load_manifest
|
|
38
39
|
|
|
@@ -52,6 +53,24 @@ class VerificationNoticeContext:
|
|
|
52
53
|
all_report_fields: Mapping[str, object] | None
|
|
53
54
|
|
|
54
55
|
|
|
56
|
+
def is_target_repository(repository_remote: str | None) -> bool:
|
|
57
|
+
"""Report whether a normalized remote names the configured target repository.
|
|
58
|
+
|
|
59
|
+
The target is read from the environment at call time and has no default,
|
|
60
|
+
so an unset or blank setting matches no repository.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
repository_remote: The normalized owner/name remote, or None.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
True when the remote equals the configured target, case-insensitively.
|
|
67
|
+
"""
|
|
68
|
+
configured_target = os.environ.get(TARGET_REPOSITORY_ENVIRONMENT_VARIABLE, "").strip()
|
|
69
|
+
if not configured_target or repository_remote is None:
|
|
70
|
+
return False
|
|
71
|
+
return repository_remote.casefold() == configured_target.casefold()
|
|
72
|
+
|
|
73
|
+
|
|
55
74
|
def normalize_repository_remote(remote_url: str) -> str | None:
|
|
56
75
|
"""Return an owner/repository slug from common Git remote URL forms.
|
|
57
76
|
|
|
@@ -85,7 +104,7 @@ def _load_notice_context(
|
|
|
85
104
|
normalized_remote = normalize_repository_remote(
|
|
86
105
|
_run_git_query(repository_root, ALL_GIT_REMOTE_URL_QUERY, False) or ""
|
|
87
106
|
)
|
|
88
|
-
if normalized_remote
|
|
107
|
+
if not is_target_repository(normalized_remote) or current_head is None:
|
|
89
108
|
return None
|
|
90
109
|
if not git_directory_text:
|
|
91
110
|
return None
|
|
@@ -20,9 +20,8 @@ from git_hooks_constants.verification_notice_constants import (
|
|
|
20
20
|
RUNNER_FILE_NAME,
|
|
21
21
|
RUNNER_PYTHON_FIELD,
|
|
22
22
|
RUNNER_SETTINGS_FIELD,
|
|
23
|
-
TARGET_REPOSITORY_REMOTE,
|
|
24
23
|
)
|
|
25
|
-
from verification_notice_context import VerificationNoticeContext
|
|
24
|
+
from verification_notice_context import VerificationNoticeContext, is_target_repository
|
|
26
25
|
|
|
27
26
|
_subprocess_window_hooks_directory = str(Path(__file__).resolve().parents[2] / "hooks")
|
|
28
27
|
if _subprocess_window_hooks_directory not in sys.path:
|
|
@@ -33,7 +32,7 @@ from hooks_constants.subprocess_window import hidden_window_creation_flags
|
|
|
33
32
|
|
|
34
33
|
def start_automatic_advisory(context: VerificationNoticeContext) -> None:
|
|
35
34
|
"""Start the detached advisory poller when native metadata is valid."""
|
|
36
|
-
if context.repository_remote
|
|
35
|
+
if not is_target_repository(context.repository_remote):
|
|
37
36
|
return
|
|
38
37
|
if context.event not in ALL_NOTICE_EVENTS:
|
|
39
38
|
return
|
package/package.json
CHANGED
|
@@ -67,9 +67,10 @@ server write. Pass the matching action and body file. Use `pr-create`,
|
|
|
67
67
|
|
|
68
68
|
Pass `--repository <owner>/<name>` for the repository the post targets. A
|
|
69
69
|
post may name a private organization only inside a repository that
|
|
70
|
-
organization owns. The
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
organization owns. The same digests cover the owner's private repository, the
|
|
71
|
+
owner's personal handle, and a private client. The linter holds the names as
|
|
72
|
+
digests, so it reports the line that names one without printing the name.
|
|
73
|
+
Describe the name in general terms and drop the link.
|
|
73
74
|
|
|
74
75
|
The linter reads the body file and reports a volatile local path without
|
|
75
76
|
printing the body. Fix the body and rerun the linter before posting.
|
|
@@ -31,6 +31,7 @@ import json
|
|
|
31
31
|
import os
|
|
32
32
|
import subprocess
|
|
33
33
|
import sys
|
|
34
|
+
from collections.abc import Callable
|
|
34
35
|
from dataclasses import dataclass
|
|
35
36
|
from datetime import datetime, timezone
|
|
36
37
|
from pathlib import Path
|
|
@@ -214,11 +215,11 @@ def _unlink_orphaned_links(main_home: Path, profile_home: Path) -> tuple[str, ..
|
|
|
214
215
|
|
|
215
216
|
|
|
216
217
|
def _unlink_local_names_linked_to_main(
|
|
217
|
-
main_home: Path, profile_home: Path
|
|
218
|
+
main_home: Path, profile_home: Path, is_local: Callable[[str], bool]
|
|
218
219
|
) -> tuple[str, ...]:
|
|
219
220
|
all_unlinked: list[str] = []
|
|
220
221
|
for each_entry in sorted(profile_home.iterdir()):
|
|
221
|
-
if not
|
|
222
|
+
if not is_local(each_entry.name):
|
|
222
223
|
continue
|
|
223
224
|
if not links_to(each_entry, main_home / each_entry.name):
|
|
224
225
|
continue
|
|
@@ -237,13 +238,16 @@ def _clear_entry(entry_path: Path, source_path: Path, replaced_directory: Path)
|
|
|
237
238
|
|
|
238
239
|
|
|
239
240
|
def _link_shared_entries(
|
|
240
|
-
main_home: Path,
|
|
241
|
+
main_home: Path,
|
|
242
|
+
profile_home: Path,
|
|
243
|
+
replaced_directory: Path,
|
|
244
|
+
is_local: Callable[[str], bool],
|
|
241
245
|
) -> tuple[tuple[str, ...], tuple[str, ...]]:
|
|
242
246
|
all_linked: list[str] = []
|
|
243
247
|
all_moved_aside: list[str] = []
|
|
244
248
|
for each_source in sorted(main_home.iterdir()):
|
|
245
249
|
entry_path = profile_home / each_source.name
|
|
246
|
-
if
|
|
250
|
+
if is_local(each_source.name) or links_to(entry_path, each_source):
|
|
247
251
|
continue
|
|
248
252
|
if os.path.lexists(entry_path) and _clear_entry(
|
|
249
253
|
entry_path, each_source, replaced_directory
|
|
@@ -255,14 +259,19 @@ def _link_shared_entries(
|
|
|
255
259
|
|
|
256
260
|
|
|
257
261
|
def sync_profile(
|
|
258
|
-
*,
|
|
262
|
+
*,
|
|
263
|
+
main_home: Path,
|
|
264
|
+
profile_home: Path,
|
|
265
|
+
now: datetime,
|
|
266
|
+
is_local: Callable[[str], bool] = is_account_local,
|
|
259
267
|
) -> ProfileSyncReport:
|
|
260
268
|
"""Link every shared main-home entry into the profile home.
|
|
261
269
|
|
|
262
270
|
Args:
|
|
263
|
-
main_home: The main account's
|
|
264
|
-
profile_home: The
|
|
271
|
+
main_home: The main account's home.
|
|
272
|
+
profile_home: The profile account's home.
|
|
265
273
|
now: The run time that names the folder stale entries move into.
|
|
274
|
+
is_local: Tells whether a top-level entry belongs to one account only.
|
|
266
275
|
|
|
267
276
|
Returns:
|
|
268
277
|
The entries the run linked, moved aside, and unlinked.
|
|
@@ -271,11 +280,14 @@ def sync_profile(
|
|
|
271
280
|
all_unlinked = tuple(
|
|
272
281
|
sorted(
|
|
273
282
|
_unlink_orphaned_links(main_home, profile_home)
|
|
274
|
-
+ _unlink_local_names_linked_to_main(main_home, profile_home)
|
|
283
|
+
+ _unlink_local_names_linked_to_main(main_home, profile_home, is_local)
|
|
275
284
|
)
|
|
276
285
|
)
|
|
277
286
|
all_linked, all_moved_aside = _link_shared_entries(
|
|
278
|
-
main_home,
|
|
287
|
+
main_home,
|
|
288
|
+
profile_home,
|
|
289
|
+
profile_home / REPLACED_DIRECTORY_NAME / _stamp(now),
|
|
290
|
+
is_local,
|
|
279
291
|
)
|
|
280
292
|
return ProfileSyncReport(
|
|
281
293
|
all_linked=all_linked,
|