claude-dev-env 8.15.0 → 8.16.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.
Files changed (115) hide show
  1. package/.agents/agents/test_agent_frontmatter.py +0 -72
  2. package/.agents/agents-archived/code-quality-agent.md +16 -16
  3. package/.agents/skills/_shared/end-of-run-gotcha-recommendations.md +5 -21
  4. package/.agents/skills/e-code-review/SKILL.md +1 -3
  5. package/.agents/skills/e-simplify/SKILL.md +6 -6
  6. package/.agents/skills/eli5/SKILL.md +4 -14
  7. package/.agents/skills/eli5/reference/task-seeds.md +3 -4
  8. package/.agents/skills/everything-search/SKILL.md +2 -2
  9. package/.agents/skills/fresh-branch/SKILL.md +2 -3
  10. package/.agents/skills/grok-spawn/SKILL.md +3 -8
  11. package/.agents/skills/issue-tracker/SKILL.md +4 -6
  12. package/.agents/skills/orchestrator-refresh/SKILL.md +7 -7
  13. package/.agents/skills/plugin-eval-standalone-skill/SKILL.md +3 -3
  14. package/.agents/skills/privacy-hygiene/SKILL.md +6 -10
  15. package/.agents/skills/privacy-hygiene/reference/sweep-procedure.md +1 -1
  16. package/.agents/skills/pull-request/SKILL.md +11 -14
  17. package/.agents/skills/pull-request/reference/publication-tasks.md +2 -4
  18. package/.agents/skills/recovering-codex-startup/SKILL.md +3 -6
  19. package/.agents/skills/recovering-codex-startup/reference/task-seeds.md +1 -1
  20. package/.agents/skills/repairing-hook-boundaries/SKILL.md +4 -7
  21. package/.agents/skills/repairing-hook-boundaries/reference/task-seeds.md +1 -2
  22. package/.agents/skills/second-account-workers/SKILL.md +26 -8
  23. package/.agents/skills/syncing-submodules/SKILL.md +7 -17
  24. package/.agents/skills/syncing-submodules/reference/run-tasks.md +1 -1
  25. package/.agents/skills/task-build/SKILL.md +1 -1
  26. package/.agents/skills/task-build/reference/tool-routing.md +1 -1
  27. package/.agents/skills/team-advisor/SKILL.md +2 -4
  28. package/.agents/skills/usage-pause/SKILL.md +1 -1
  29. package/.agents/skills/windows-scheduled-task/SKILL.md +1 -1
  30. package/AGENTS.md +1 -1
  31. package/_shared/advisor/advisor-protocol.md +4 -4
  32. package/_shared/advisor/reference/consult-format.md +1 -1
  33. package/_shared/advisor/reference/identity.md +2 -2
  34. package/_shared/advisor/reference/warm-up.md +1 -1
  35. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
  36. package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +2 -16
  37. package/bin/install.mjs +1 -0
  38. package/bin/install.prompt-context.test.mjs +7 -3
  39. package/bin/install.test.mjs +3 -2
  40. package/commands/sr-loop.md +2 -2
  41. package/docs/CODE_RULES.md +2 -2
  42. package/docs/DJANGO_PATTERNS.md +3 -3
  43. package/docs/REACT_PATTERNS.md +6 -6
  44. package/docs/TEST_QUALITY.md +10 -16
  45. package/docs/agent-spawn-protocol.md +4 -4
  46. package/docs/codex-compatibility.md +4 -33
  47. package/docs/references/team-advisor-skill.md +0 -1
  48. package/docs/worker-completion-gate.md +0 -1
  49. package/hooks/advisory/migration_safety_advisor.py +3 -2
  50. package/hooks/advisory/refactor_guard.py +3 -3
  51. package/hooks/hooks.json +1 -1
  52. package/hooks/hooks_constants/issue_tracker_session_starter_constants.py +1 -2
  53. package/hooks/hooks_constants/orchestrator_auto_starter_constants.py +1 -2
  54. package/package.json +1 -1
  55. package/rules/explore-thoroughly.md +0 -1
  56. package/rules/filesystem-search.md +1 -1
  57. package/rules/git-workflow.md +5 -5
  58. package/rules/long-horizon-autonomy.md +1 -5
  59. package/rules/re-stage-before-commit.md +0 -2
  60. package/rules/shell-invocation.md +1 -1
  61. package/scripts/claude_account_choice.py +183 -52
  62. package/scripts/claude_account_profile.py +81 -49
  63. package/scripts/claude_account_worker.py +48 -15
  64. package/scripts/dev_env_scripts_constants/claude_account_constants.py +36 -1
  65. package/scripts/dev_env_scripts_constants/claude_account_worker_constants.py +2 -0
  66. package/scripts/dev_env_scripts_constants/grok_worker_constants.py +8 -2
  67. package/scripts/policy_lint/config/constants.py +1 -0
  68. package/scripts/policy_lint/engine.py +5 -2
  69. package/scripts/repository_checks/config/constants.py +1 -1
  70. package/scripts/spawn_grok_batch.py +12 -1
  71. package/scripts/test_claude_account_choice.py +127 -17
  72. package/scripts/test_claude_account_profile.py +63 -16
  73. package/scripts/test_claude_account_worker.py +118 -0
  74. package/scripts/test_spawn_grok_batch.py +22 -0
  75. package/scripts/tests/test_banned_prose_words.py +1 -1
  76. package/scripts/tests/test_policy_lint_rules_archive.py +16 -0
  77. package/scripts/tests/test_policy_lint_rules_engine.py +2 -2
  78. package/scripts/tests/test_rules.py +9 -9
  79. package/system-prompts/software-engineer.xml +15 -30
  80. package/audit-rubrics/audit-categories.json +0 -700
  81. package/audit-rubrics/category_rubrics/category-a-api-contracts.md +0 -90
  82. package/audit-rubrics/category_rubrics/category-b-selector-engine-compat.md +0 -36
  83. package/audit-rubrics/category_rubrics/category-c-resource-cleanup.md +0 -35
  84. package/audit-rubrics/category_rubrics/category-d-scoping-and-ordering.md +0 -35
  85. package/audit-rubrics/category_rubrics/category-e-dead-code.md +0 -40
  86. package/audit-rubrics/category_rubrics/category-f-silent-failures.md +0 -41
  87. package/audit-rubrics/category_rubrics/category-g-bounds-and-overflow.md +0 -38
  88. package/audit-rubrics/category_rubrics/category-h-security-boundaries.md +0 -40
  89. package/audit-rubrics/category_rubrics/category-i-concurrency.md +0 -38
  90. package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +0 -59
  91. package/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md +0 -66
  92. package/audit-rubrics/category_rubrics/category-l-behavior-equivalence.md +0 -48
  93. package/audit-rubrics/category_rubrics/category-m-producer-consumer-cardinality.md +0 -44
  94. package/audit-rubrics/category_rubrics/category-n-test-name-scenario-verifier.md +0 -54
  95. package/audit-rubrics/category_rubrics/category-p-name-vs-behavior-contract.md +0 -59
  96. package/audit-rubrics/category_rubrics/category-q-cross-surface-claims.md +0 -46
  97. package/audit-rubrics/prompts/category-a-api-contracts.md +0 -407
  98. package/audit-rubrics/prompts/category-b-selector-engine-compat.md +0 -401
  99. package/audit-rubrics/prompts/category-c-resource-cleanup.md +0 -420
  100. package/audit-rubrics/prompts/category-d-scoping-and-ordering.md +0 -414
  101. package/audit-rubrics/prompts/category-e-dead-code.md +0 -434
  102. package/audit-rubrics/prompts/category-f-silent-failures.md +0 -432
  103. package/audit-rubrics/prompts/category-g-bounds-and-overflow.md +0 -383
  104. package/audit-rubrics/prompts/category-h-security-boundaries.md +0 -423
  105. package/audit-rubrics/prompts/category-i-concurrency.md +0 -429
  106. package/audit-rubrics/prompts/category-j-code-rules-compliance.md +0 -458
  107. package/audit-rubrics/prompts/category-k-codebase-conflicts.md +0 -338
  108. package/audit-rubrics/prompts/category-l-behavior-equivalence.md +0 -131
  109. package/audit-rubrics/prompts/category-m-producer-consumer-cardinality.md +0 -129
  110. package/audit-rubrics/prompts/category-n-test-name-scenario-verifier.md +0 -139
  111. package/audit-rubrics/prompts/category-p-name-vs-behavior-contract.md +0 -89
  112. package/audit-rubrics/prompts/category-q-cross-surface-claims.md +0 -51
  113. package/hooks/hooks_constants/style_reminder_prompt_constants.py +0 -9
  114. package/hooks/session/style_reminder_prompt.py +0 -55
  115. package/hooks/session/test_style_reminder_prompt.py +0 -86
@@ -30,7 +30,7 @@ def meters(
30
30
 
31
31
 
32
32
  class TestChooseAccount:
33
- def should_pick_main_in_jons_example(self) -> None:
33
+ def test_should_pick_main_when_week_resets_soon(self) -> None:
34
34
  main_meters = meters(
35
35
  session_used=10.0, weekly_used=80.0, weekly_resets_in=timedelta(hours=5)
36
36
  )
@@ -42,7 +42,7 @@ class TestChooseAccount:
42
42
  )
43
43
  assert decision.account == choice.CHOICE_MAIN
44
44
 
45
- def should_pick_second_when_main_week_resets_later_than_a_day(self) -> None:
45
+ def test_should_pick_second_when_main_week_resets_later_than_a_day(self) -> None:
46
46
  main_meters = meters(
47
47
  session_used=0.0, weekly_used=5.0, weekly_resets_in=timedelta(hours=30)
48
48
  )
@@ -54,7 +54,7 @@ class TestChooseAccount:
54
54
  )
55
55
  assert decision.account == choice.CHOICE_SECOND
56
56
 
57
- def should_keep_main_once_it_nears_its_weekly_ceiling(self) -> None:
57
+ def test_should_keep_main_once_it_nears_its_weekly_ceiling(self) -> None:
58
58
  main_meters = meters(
59
59
  session_used=0.0, weekly_used=90.0, weekly_resets_in=timedelta(hours=3)
60
60
  )
@@ -66,7 +66,7 @@ class TestChooseAccount:
66
66
  )
67
67
  assert decision.account == choice.CHOICE_SECOND
68
68
 
69
- def should_keep_main_when_its_five_hour_window_is_half_spent(self) -> None:
69
+ def test_should_keep_main_when_its_five_hour_window_is_half_spent(self) -> None:
70
70
  main_meters = meters(
71
71
  session_used=50.0, weekly_used=20.0, weekly_resets_in=timedelta(hours=3)
72
72
  )
@@ -78,7 +78,7 @@ class TestChooseAccount:
78
78
  )
79
79
  assert decision.account == choice.CHOICE_SECOND
80
80
 
81
- def should_never_pick_main_when_its_meter_is_unread(self) -> None:
81
+ def test_should_never_pick_main_when_its_meter_is_unread(self) -> None:
82
82
  second_meters = meters(
83
83
  session_used=95.0, weekly_used=99.0, weekly_resets_in=timedelta(days=4)
84
84
  )
@@ -87,7 +87,7 @@ class TestChooseAccount:
87
87
  )
88
88
  assert decision.account == choice.CHOICE_WAIT
89
89
 
90
- def should_never_pick_main_when_its_reset_time_is_missing(self) -> None:
90
+ def test_should_never_pick_main_when_its_reset_time_is_missing(self) -> None:
91
91
  main_meters = meters(session_used=0.0, weekly_used=0.0, weekly_resets_in=None)
92
92
  second_meters = meters(
93
93
  session_used=95.0, weekly_used=99.0, weekly_resets_in=timedelta(days=4)
@@ -97,12 +97,12 @@ class TestChooseAccount:
97
97
  )
98
98
  assert decision.account == choice.CHOICE_WAIT
99
99
 
100
- def should_pick_second_when_its_meter_is_unread(self) -> None:
100
+ def test_should_pick_second_when_its_meter_is_unread(self) -> None:
101
101
  decision = choice.choose_account(main_meters=None, second_meters=None, now=NOW)
102
102
  assert decision.account == choice.CHOICE_SECOND
103
103
  assert decision.reason == choice.REASON_SECOND_UNREAD
104
104
 
105
- def should_wait_and_name_the_session_reset_when_the_five_hour_window_blocks(
105
+ def test_should_wait_and_name_the_session_reset_when_the_five_hour_window_blocks(
106
106
  self,
107
107
  ) -> None:
108
108
  second_meters = meters(
@@ -117,7 +117,7 @@ class TestChooseAccount:
117
117
  assert decision.account == choice.CHOICE_WAIT
118
118
  assert (NOW + timedelta(hours=1)).isoformat() in decision.reason
119
119
 
120
- def should_wait_and_name_the_weekly_reset_when_the_week_blocks(self) -> None:
120
+ def test_should_wait_and_name_the_weekly_reset_when_the_week_blocks(self) -> None:
121
121
  second_meters = meters(
122
122
  session_used=10.0, weekly_used=95.0, weekly_resets_in=timedelta(days=2)
123
123
  )
@@ -129,7 +129,7 @@ class TestChooseAccount:
129
129
 
130
130
 
131
131
  class TestReadAccountMeters:
132
- def should_return_none_when_the_probe_cannot_read_the_account(
132
+ def test_should_return_none_when_the_probe_cannot_read_the_account(
133
133
  self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
134
134
  ) -> None:
135
135
  def failing_probe(credentials_path: Path) -> AccountUsageMeters:
@@ -138,7 +138,7 @@ class TestReadAccountMeters:
138
138
  monkeypatch.setattr(choice, "probe_account_meters", failing_probe)
139
139
  assert choice.read_account_meters(tmp_path / ".credentials.json") is None
140
140
 
141
- def should_return_the_probed_meters(
141
+ def test_should_return_the_probed_meters(
142
142
  self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
143
143
  ) -> None:
144
144
  probed = meters(
@@ -149,7 +149,7 @@ class TestReadAccountMeters:
149
149
 
150
150
 
151
151
  class TestDecisionPayload:
152
- def should_name_no_config_directory_for_a_wait(self) -> None:
152
+ def test_should_name_no_config_directory_for_a_wait(self) -> None:
153
153
  decision = choice.AccountDecision(account=choice.CHOICE_WAIT, reason="full")
154
154
  assert choice.decision_payload(decision, config_directory=None) == {
155
155
  "account": "wait",
@@ -159,10 +159,10 @@ class TestDecisionPayload:
159
159
 
160
160
 
161
161
  class TestMetersPayload:
162
- def should_name_no_meters_for_an_unread_account(self) -> None:
162
+ def test_should_name_no_meters_for_an_unread_account(self) -> None:
163
163
  assert choice.meters_payload(None) is None
164
164
 
165
- def should_carry_each_used_percent_and_reset_time(self) -> None:
165
+ def test_should_carry_each_used_percent_and_reset_time(self) -> None:
166
166
  read_meters = meters(
167
167
  session_used=12.0,
168
168
  weekly_used=34.0,
@@ -176,7 +176,7 @@ class TestMetersPayload:
176
176
  "weekly_resets_at": "2026-09-25T21:00:00+00:00",
177
177
  }
178
178
 
179
- def should_name_no_reset_time_the_meter_left_out(self) -> None:
179
+ def test_should_name_no_reset_time_the_meter_left_out(self) -> None:
180
180
  read_meters = meters(session_used=1.0, weekly_used=None, weekly_resets_in=None)
181
181
  payload = choice.meters_payload(read_meters)
182
182
  assert payload is not None
@@ -185,7 +185,7 @@ class TestMetersPayload:
185
185
 
186
186
 
187
187
  class TestMain:
188
- def should_print_the_choice_and_the_config_directory_of_the_chosen_account(
188
+ def test_should_print_the_choice_and_the_config_directory_of_the_chosen_account(
189
189
  self,
190
190
  tmp_path: Path,
191
191
  monkeypatch: pytest.MonkeyPatch,
@@ -219,7 +219,7 @@ class TestMain:
219
219
  assert printed["meters"]["main"] is None
220
220
  assert printed["meters"]["second"]["weekly_used_percent"] == 10.0
221
221
 
222
- def should_print_no_config_directory_when_the_job_waits(
222
+ def test_should_print_no_config_directory_when_the_job_waits(
223
223
  self,
224
224
  tmp_path: Path,
225
225
  monkeypatch: pytest.MonkeyPatch,
@@ -243,3 +243,113 @@ class TestMain:
243
243
  assert exit_code == 0
244
244
  assert printed["account"] == choice.CHOICE_WAIT
245
245
  assert printed["config_dir"] is None
246
+
247
+
248
+ def test_should_pick_second_extra_in_order_and_print_every_meter(
249
+ tmp_path: Path,
250
+ monkeypatch: pytest.MonkeyPatch,
251
+ capsys: pytest.CaptureFixture[str],
252
+ ) -> None:
253
+ main_home = tmp_path / "main"
254
+ first_extra = tmp_path / "first"
255
+ second_extra = tmp_path / "next"
256
+ third_extra = tmp_path / "last"
257
+ readings = {
258
+ main_home: None,
259
+ first_extra: meters(
260
+ session_used=90.0, weekly_used=95.0, weekly_resets_in=timedelta(days=4)
261
+ ),
262
+ second_extra: meters(
263
+ session_used=10.0, weekly_used=20.0, weekly_resets_in=timedelta(days=4)
264
+ ),
265
+ third_extra: meters(
266
+ session_used=30.0, weekly_used=40.0, weekly_resets_in=timedelta(days=4)
267
+ ),
268
+ }
269
+ monkeypatch.setattr(
270
+ choice, "read_account_meters", lambda path: readings[path.parent]
271
+ )
272
+
273
+ assert choice.main(
274
+ [
275
+ "--main-config-dir",
276
+ str(main_home),
277
+ "--second-config-dir",
278
+ str(first_extra),
279
+ "--extra-config-dir",
280
+ str(second_extra),
281
+ "--extra-config-dir",
282
+ str(third_extra),
283
+ ]
284
+ ) == 0
285
+ printed = json.loads(capsys.readouterr().out)
286
+
287
+ assert printed["account"] == "extra_2"
288
+ assert printed["config_dir"] == str(second_extra)
289
+ assert list(printed["meters"]) == ["main", "second", "extra_2", "extra_3"]
290
+ assert printed["meters"]["main"] is None
291
+ assert printed["meters"]["second"]["weekly_used_percent"] == 95.0
292
+ assert printed["meters"]["extra_2"]["weekly_used_percent"] == 20.0
293
+ assert printed["meters"]["extra_3"]["weekly_used_percent"] == 40.0
294
+
295
+
296
+ def test_should_wait_for_first_usable_reset_when_all_extras_are_full(
297
+ tmp_path: Path,
298
+ monkeypatch: pytest.MonkeyPatch,
299
+ capsys: pytest.CaptureFixture[str],
300
+ ) -> None:
301
+ first_extra = tmp_path / "first"
302
+ second_extra = tmp_path / "next"
303
+ third_extra = tmp_path / "last"
304
+ readings = {
305
+ first_extra: meters(
306
+ session_used=90.0,
307
+ weekly_used=95.0,
308
+ weekly_resets_in=timedelta(hours=1),
309
+ session_resets_in=timedelta(hours=3),
310
+ ),
311
+ second_extra: meters(
312
+ session_used=10.0, weekly_used=95.0, weekly_resets_in=timedelta(hours=2)
313
+ ),
314
+ third_extra: meters(
315
+ session_used=90.0, weekly_used=10.0, weekly_resets_in=timedelta(days=4),
316
+ session_resets_in=timedelta(hours=4),
317
+ ),
318
+ }
319
+ monkeypatch.setattr(
320
+ choice, "read_account_meters", lambda path: readings.get(path.parent)
321
+ )
322
+
323
+ assert choice.main(
324
+ [
325
+ "--main-config-dir",
326
+ str(tmp_path / "main"),
327
+ "--second-config-dir",
328
+ str(first_extra),
329
+ "--extra-config-dir",
330
+ str(second_extra),
331
+ "--extra-config-dir",
332
+ str(third_extra),
333
+ ]
334
+ ) == 0
335
+ printed = json.loads(capsys.readouterr().out)
336
+
337
+ assert printed["account"] == choice.CHOICE_WAIT
338
+ assert printed["config_dir"] is None
339
+ assert (NOW + timedelta(hours=2)).isoformat() in printed["reason"]
340
+ assert (NOW + timedelta(hours=1)).isoformat() not in printed["reason"]
341
+ assert list(printed["meters"]) == ["main", "second", "extra_2", "extra_3"]
342
+
343
+
344
+ def test_should_reject_duplicate_extra_config_directories(tmp_path: Path) -> None:
345
+ with pytest.raises(SystemExit) as error:
346
+ choice.main(
347
+ [
348
+ "--second-config-dir",
349
+ str(tmp_path / "first"),
350
+ "--extra-config-dir",
351
+ str(tmp_path / "first"),
352
+ ]
353
+ )
354
+
355
+ assert error.value.code == 2
@@ -32,13 +32,13 @@ def sync(main_home: Path, profile_home: Path) -> profile.ProfileSyncReport:
32
32
 
33
33
 
34
34
  class TestDefaultProfileHome:
35
- def should_use_the_profiles_root_the_environment_names(
35
+ def test_should_use_the_profiles_root_the_environment_names(
36
36
  self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
37
37
  ) -> None:
38
38
  monkeypatch.setenv("LLM_SETTINGS_PROFILES_ROOT", str(tmp_path))
39
39
  assert profile.default_profile_home() == tmp_path / "ev"
40
40
 
41
- def should_fall_back_to_the_profiles_root_in_the_user_home(
41
+ def test_should_fall_back_to_the_profiles_root_in_the_user_home(
42
42
  self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
43
43
  ) -> None:
44
44
  monkeypatch.delenv("LLM_SETTINGS_PROFILES_ROOT", raising=False)
@@ -47,23 +47,24 @@ class TestDefaultProfileHome:
47
47
 
48
48
 
49
49
  class TestIsAccountLocal:
50
- def should_keep_sign_in_state_and_history_local(self) -> None:
50
+ def test_should_keep_sign_in_state_and_history_local(self) -> None:
51
51
  for each_name in (
52
52
  ".credentials.json",
53
53
  ".credentials.json.bak-session-dashboard",
54
54
  ".claude.json",
55
55
  ".claude.json.backup",
56
+ "extra-profiles.json",
56
57
  "projects",
57
58
  ):
58
59
  assert profile.is_account_local(each_name)
59
60
 
60
- def should_share_skills_rules_and_settings(self) -> None:
61
+ def test_should_share_skills_rules_and_settings(self) -> None:
61
62
  for each_name in ("skills", "rules", "CLAUDE.md", "settings.json", "plugins"):
62
63
  assert not profile.is_account_local(each_name)
63
64
 
64
65
 
65
66
  class TestSyncProfile:
66
- def should_link_every_shared_entry_to_the_main_home(self, tmp_path: Path) -> None:
67
+ def test_should_link_every_shared_entry_to_the_main_home(self, tmp_path: Path) -> None:
67
68
  main_home = build_main_home(tmp_path)
68
69
  profile_home = tmp_path / "ev"
69
70
  sync(main_home, profile_home)
@@ -76,7 +77,7 @@ class TestSyncProfile:
76
77
  profile_home / "settings.json", main_home / "settings.json"
77
78
  )
78
79
 
79
- def should_link_only_what_the_callers_rule_shares(self, tmp_path: Path) -> None:
80
+ def test_should_link_only_what_the_callers_rule_shares(self, tmp_path: Path) -> None:
80
81
  main_home = build_main_home(tmp_path)
81
82
  profile_home = tmp_path / "codex-1"
82
83
  report = profile.sync_profile(
@@ -88,7 +89,7 @@ class TestSyncProfile:
88
89
  assert report.all_linked == ("rules",)
89
90
  assert not (profile_home / "skills").exists()
90
91
 
91
- def should_keep_the_accounts_own_sign_in_state_and_history_apart(
92
+ def test_should_keep_the_accounts_own_sign_in_state_and_history_apart(
92
93
  self, tmp_path: Path
93
94
  ) -> None:
94
95
  main_home = build_main_home(tmp_path)
@@ -105,7 +106,7 @@ class TestSyncProfile:
105
106
  assert not (profile_home / ".claude.json.backup").exists()
106
107
  assert not (profile_home / "projects").exists()
107
108
 
108
- def should_move_a_stale_copy_aside_and_keep_its_content(
109
+ def test_should_move_a_stale_copy_aside_and_keep_its_content(
109
110
  self, tmp_path: Path
110
111
  ) -> None:
111
112
  main_home = build_main_home(tmp_path)
@@ -120,7 +121,7 @@ class TestSyncProfile:
120
121
  assert profile.links_to(profile_home / "CLAUDE.md", main_home / "CLAUDE.md")
121
122
  assert report.all_moved_aside == ("CLAUDE.md",)
122
123
 
123
- def should_remove_a_same_content_copy_without_moving_it_aside(
124
+ def test_should_remove_a_same_content_copy_without_moving_it_aside(
124
125
  self, tmp_path: Path
125
126
  ) -> None:
126
127
  main_home = build_main_home(tmp_path)
@@ -132,7 +133,7 @@ class TestSyncProfile:
132
133
  assert not (profile_home / ".replaced").exists()
133
134
  assert profile.links_to(profile_home / "CLAUDE.md", main_home / "CLAUDE.md")
134
135
 
135
- def should_move_aside_a_link_that_points_somewhere_else(
136
+ def test_should_move_aside_a_link_that_points_somewhere_else(
136
137
  self, tmp_path: Path
137
138
  ) -> None:
138
139
  main_home = build_main_home(tmp_path)
@@ -147,7 +148,7 @@ class TestSyncProfile:
147
148
  assert profile.links_to(profile_home / "rules", main_home / "rules")
148
149
  assert report.all_moved_aside == ("rules",)
149
150
 
150
- def should_change_nothing_on_a_second_run(self, tmp_path: Path) -> None:
151
+ def test_should_change_nothing_on_a_second_run(self, tmp_path: Path) -> None:
151
152
  main_home = build_main_home(tmp_path)
152
153
  profile_home = tmp_path / "ev"
153
154
  sync(main_home, profile_home)
@@ -156,7 +157,7 @@ class TestSyncProfile:
156
157
  assert second_report.all_moved_aside == ()
157
158
  assert second_report.all_unlinked == ()
158
159
 
159
- def should_unlink_an_entry_the_main_home_no_longer_has(
160
+ def test_should_unlink_an_entry_the_main_home_no_longer_has(
160
161
  self, tmp_path: Path
161
162
  ) -> None:
162
163
  main_home = build_main_home(tmp_path)
@@ -168,7 +169,7 @@ class TestSyncProfile:
168
169
  assert report.all_unlinked == ("rules",)
169
170
 
170
171
 
171
- def should_remove_a_main_link_left_under_an_account_local_name(
172
+ def test_should_remove_a_main_link_left_under_an_account_local_name(
172
173
  self, tmp_path: Path
173
174
  ) -> None:
174
175
  main_home = build_main_home(tmp_path)
@@ -185,7 +186,7 @@ class TestSyncProfile:
185
186
 
186
187
 
187
188
  class TestWriteLauncher:
188
- def should_point_claude_at_the_profile_and_pass_every_argument(
189
+ def test_should_point_claude_at_the_profile_and_pass_every_argument(
189
190
  self, tmp_path: Path
190
191
  ) -> None:
191
192
  profile_home = tmp_path / "ev"
@@ -196,7 +197,7 @@ class TestWriteLauncher:
196
197
  assert f'set "CLAUDE_CONFIG_DIR={profile_home}"' in launcher_text
197
198
  assert "claude %*" in launcher_text
198
199
 
199
- def should_move_an_older_launcher_aside(self, tmp_path: Path) -> None:
200
+ def test_should_move_an_older_launcher_aside(self, tmp_path: Path) -> None:
200
201
  launcher_directory = tmp_path / "bin"
201
202
  launcher_directory.mkdir()
202
203
  (launcher_directory / "claude-ev.cmd").write_text(
@@ -212,7 +213,7 @@ class TestWriteLauncher:
212
213
 
213
214
 
214
215
  class TestMain:
215
- def should_print_what_the_sync_changed(
216
+ def test_should_print_what_the_sync_changed(
216
217
  self, tmp_path: Path, capsys: pytest.CaptureFixture[str]
217
218
  ) -> None:
218
219
  main_home = build_main_home(tmp_path)
@@ -230,3 +231,49 @@ class TestMain:
230
231
  assert exit_code == 0
231
232
  assert "CLAUDE.md" in printed["linked"]
232
233
  assert printed["launcher"] == str(tmp_path / "bin" / "claude-ev.cmd")
234
+
235
+
236
+ def test_should_create_named_profile_and_launcher_without_changes_on_repeat(
237
+ tmp_path: Path,
238
+ monkeypatch: pytest.MonkeyPatch,
239
+ capsys: pytest.CaptureFixture[str],
240
+ ) -> None:
241
+ main_home = build_main_home(tmp_path)
242
+ (main_home / "extra-profiles.json").write_text("[]", encoding="utf-8")
243
+ profiles_root = tmp_path / "profiles"
244
+ launcher_directory = tmp_path / "bin"
245
+ monkeypatch.setenv("LLM_SETTINGS_PROFILES_ROOT", str(profiles_root))
246
+ arguments = [
247
+ "--main-home",
248
+ str(main_home),
249
+ "--profile-name",
250
+ "profile-3",
251
+ "--launcher-directory",
252
+ str(launcher_directory),
253
+ ]
254
+
255
+ assert profile.main(arguments) == 0
256
+ first_report = json.loads(capsys.readouterr().out)
257
+ profile_home = profiles_root / "profile-3"
258
+ launcher_path = launcher_directory / "claude-profile-3.cmd"
259
+ assert profile.links_to(profile_home / "CLAUDE.md", main_home / "CLAUDE.md")
260
+ assert not (profile_home / "extra-profiles.json").exists()
261
+ assert first_report["launcher"] == str(launcher_path)
262
+ assert f'set "CLAUDE_CONFIG_DIR={profile_home}"' in launcher_path.read_text(
263
+ encoding="utf-8"
264
+ )
265
+
266
+ assert profile.main(arguments) == 0
267
+ second_report = json.loads(capsys.readouterr().out)
268
+ assert second_report["linked"] == []
269
+ assert second_report["moved_aside"] == []
270
+ assert second_report["unlinked"] == []
271
+ assert list(launcher_directory.glob("*.replaced-*")) == []
272
+
273
+
274
+ @pytest.mark.parametrize(
275
+ "profile_name", ["../outside", "bad name", "CON", "main", "wait"]
276
+ )
277
+ def test_should_reject_unsafe_profile_names(profile_name: str) -> None:
278
+ with pytest.raises(ValueError):
279
+ profile.default_profile_home(profile_name)
@@ -3,12 +3,15 @@ from __future__ import annotations
3
3
  import json
4
4
  import subprocess
5
5
  from collections.abc import Callable
6
+ from datetime import datetime, timedelta, timezone
6
7
  from pathlib import Path
7
8
  from typing import Any
8
9
 
9
10
  import pytest
10
11
 
12
+ import claude_account_choice
11
13
  import claude_account_worker
14
+ from claude_chain_usage import AccountUsageMeters
12
15
  from claude_account_worker import (
13
16
  AccountSelection,
14
17
  WorkerDependencies,
@@ -26,6 +29,8 @@ from dev_env_scripts_constants.claude_account_worker_constants import (
26
29
  WAIT_EXIT_CODE,
27
30
  )
28
31
 
32
+ NOW = datetime(2026, 9, 22, 21, 0, tzinfo=timezone.utc)
33
+
29
34
 
30
35
  class FakeProcess:
31
36
  def __init__(
@@ -319,6 +324,119 @@ def test_should_pick_second_account_when_neither_meter_is_readable(
319
324
  assert selection.config_dir == second_home
320
325
 
321
326
 
327
+ def test_should_read_profile_list_and_pick_later_extra(
328
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
329
+ ) -> None:
330
+ main_home = tmp_path / ".claude"
331
+ main_home.mkdir()
332
+ (main_home / "extra-profiles.json").write_text(
333
+ json.dumps(["profile_a", "profile_b"]), encoding="utf-8"
334
+ )
335
+ profiles_root = tmp_path / "profiles"
336
+ monkeypatch.setattr(Path, "home", lambda: tmp_path)
337
+ monkeypatch.setenv("LLM_SETTINGS_PROFILES_ROOT", str(profiles_root))
338
+ readings = {
339
+ profiles_root / "profile_a": AccountUsageMeters(
340
+ session_utilization=90.0,
341
+ session_resets_at=NOW + timedelta(hours=2),
342
+ weekly_utilization=95.0,
343
+ weekly_resets_at=NOW + timedelta(days=2),
344
+ ),
345
+ profiles_root / "profile_b": AccountUsageMeters(
346
+ session_utilization=10.0,
347
+ session_resets_at=NOW + timedelta(hours=2),
348
+ weekly_utilization=20.0,
349
+ weekly_resets_at=NOW + timedelta(days=2),
350
+ ),
351
+ }
352
+
353
+ def read_meters(path: Path) -> AccountUsageMeters | None:
354
+ return readings.get(path.parent)
355
+
356
+ monkeypatch.setattr(claude_account_choice, "read_account_meters", read_meters)
357
+ monkeypatch.setattr(claude_account_worker, "read_account_meters", read_meters)
358
+
359
+ selection = select_account()
360
+
361
+ assert selection.account == "extra_2"
362
+ assert selection.config_dir == profiles_root / "profile_b"
363
+
364
+
365
+ def test_should_exit_three_when_every_listed_profile_is_full(
366
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
367
+ ) -> None:
368
+ main_home = tmp_path / ".claude"
369
+ main_home.mkdir()
370
+ (main_home / "extra-profiles.json").write_text(
371
+ json.dumps(["profile_a", "profile_b"]), encoding="utf-8"
372
+ )
373
+ monkeypatch.setattr(Path, "home", lambda: tmp_path)
374
+ monkeypatch.setenv("LLM_SETTINGS_PROFILES_ROOT", str(tmp_path / "profiles"))
375
+ blocked = AccountUsageMeters(
376
+ session_utilization=90.0,
377
+ session_resets_at=NOW + timedelta(hours=2),
378
+ weekly_utilization=95.0,
379
+ weekly_resets_at=NOW + timedelta(days=2),
380
+ )
381
+
382
+ def read_meters(path: Path) -> AccountUsageMeters | None:
383
+ return None if path.parent == main_home else blocked
384
+
385
+ monkeypatch.setattr(claude_account_choice, "read_account_meters", read_meters)
386
+ monkeypatch.setattr(claude_account_worker, "read_account_meters", read_meters)
387
+ report_file = tmp_path / "report.json"
388
+
389
+ exit_code = run_worker(
390
+ prompt_file=tmp_path / "unused.md",
391
+ cwd=tmp_path,
392
+ report_file=report_file,
393
+ model=None,
394
+ permission_mode="auto",
395
+ timeout_minutes=60,
396
+ decision_selector=select_account,
397
+ dependencies=WorkerDependencies(
398
+ process_factory=lambda *args, **kwargs: (_ for _ in ()).throw(
399
+ AssertionError("wait must not start a process")
400
+ ),
401
+ all_parent_environment_variables={},
402
+ binary_resolver=lambda _: None,
403
+ process_terminator=lambda _: None,
404
+ monotonic_clock=lambda: 1.0,
405
+ ),
406
+ )
407
+ report = json.loads(report_file.read_text(encoding="utf-8"))
408
+
409
+ assert exit_code == WAIT_EXIT_CODE
410
+ assert report["account"] == CHOICE_WAIT
411
+ assert (NOW + timedelta(days=2)).isoformat() in report["reason"]
412
+
413
+
414
+ def test_should_set_later_extra_profile_environment(tmp_path: Path) -> None:
415
+ profile_dir = tmp_path / "profile_b"
416
+ exit_code, report, captured = _run_worker(
417
+ tmp_path,
418
+ selection=_selection("extra_2", config_dir=profile_dir),
419
+ process=FakeProcess(),
420
+ )
421
+
422
+ assert exit_code == 0
423
+ assert report["account"] == "extra_2"
424
+ assert captured["env"][CLAUDE_CONFIG_DIR_ENV_VAR] == str(profile_dir)
425
+
426
+
427
+ @pytest.mark.parametrize(
428
+ "contents",
429
+ ['{', '{}', '[]', '["profile_a", "PROFILE_A"]', '["../outside"]', '["main"]'],
430
+ )
431
+ def test_should_reject_invalid_profile_lists(tmp_path: Path, contents: str) -> None:
432
+ main_home = tmp_path / ".claude"
433
+ main_home.mkdir()
434
+ (main_home / "extra-profiles.json").write_text(contents, encoding="utf-8")
435
+
436
+ with pytest.raises(ValueError):
437
+ claude_account_worker.extra_config_directories(main_home)
438
+
439
+
322
440
  def test_should_require_prompt_file_and_report_file_flags() -> None:
323
441
  parser = build_argument_parser()
324
442
 
@@ -1514,6 +1514,7 @@ def test_bind_unique_worker_advisor_rejects_placeholder(
1514
1514
  batch.bind_unique_worker_advisor(
1515
1515
  advisor_spec=batch.AdvisorSpec(launcher=batch.DEFAULT_ADVISOR_LAUNCHER_PLACEHOLDER),
1516
1516
  role_name="lens",
1517
+ assignment_text="assignment",
1517
1518
  all_used_session_ids=set(),
1518
1519
  )
1519
1520
 
@@ -1528,12 +1529,33 @@ def test_bind_unique_worker_advisor_returns_session(
1528
1529
  session_id, signal = batch.bind_unique_worker_advisor(
1529
1530
  advisor_spec=batch.AdvisorSpec(launcher="fixture-advisor-launcher"),
1530
1531
  role_name="lens",
1532
+ assignment_text="assignment",
1531
1533
  all_used_session_ids=set(),
1532
1534
  )
1533
1535
  assert session_id == "sess-unique-1"
1534
1536
  assert signal == "ENDORSE"
1535
1537
 
1536
1538
 
1539
+ def test_bind_unique_worker_advisor_prompt_carries_assignment_text(
1540
+ monkeypatch: pytest.MonkeyPatch,
1541
+ ) -> None:
1542
+ all_captured_prompts: list[object] = []
1543
+
1544
+ def fake(**kwargs: object) -> tuple[str | None, str, int]:
1545
+ all_captured_prompts.append(kwargs["prompt_text"])
1546
+ return "sess-assignment-1", "ENDORSE\nok", 0
1547
+
1548
+ monkeypatch.setattr(batch, "batch_invoke_advisor", fake)
1549
+ batch.bind_unique_worker_advisor(
1550
+ advisor_spec=batch.AdvisorSpec(launcher="fixture-advisor-launcher"),
1551
+ role_name="lens",
1552
+ assignment_text="Audit module alpha for unused imports.",
1553
+ all_used_session_ids=set(),
1554
+ )
1555
+ assert len(all_captured_prompts) == 1
1556
+ assert "Audit module alpha for unused imports." in str(all_captured_prompts[0])
1557
+
1558
+
1537
1559
  def test_obtain_advisor_completion_verdict_endorses(
1538
1560
  monkeypatch: pytest.MonkeyPatch,
1539
1561
  ) -> None:
@@ -86,7 +86,7 @@ def test_governed_paths_lists_the_shipped_surfaces(tmp_path: Path) -> None:
86
86
  def test_governed_paths_cover_instruction_surfaces_and_skip_archives() -> None:
87
87
  assert governs_path("rules/git-workflow.md")
88
88
  assert governs_path(".agents/skills/eli5/SKILL.md")
89
- assert governs_path("audit-rubrics/prompts/category-e-dead-code.md")
89
+ assert governs_path("audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md")
90
90
  assert governs_path("system-prompts/software-engineer.xml")
91
91
  assert not governs_path("rules-archived/manifest.md")
92
92
  assert not governs_path("scripts/banned_prose_words.py")
@@ -134,3 +134,19 @@ def test_archived_hook_moves_leave_the_automatic_checks(tmp_path: Path) -> None:
134
134
  assert runtime_set.documents == ()
135
135
  assert runtime_set.deleted_paths == (live_hook_path,)
136
136
  assert runtime_set.renamed_paths == ()
137
+
138
+
139
+ def test_archived_audit_rubric_moves_leave_the_automatic_checks(tmp_path: Path) -> None:
140
+ live_rubric_path = PurePosixPath("packages/tool/audit-rubrics/prompts/category-k.md")
141
+ archived_rubric_path = PurePosixPath("packages/tool/audit-rubrics-archived/prompts/category-k.md")
142
+ document_set = DocumentSet(
143
+ (Document.from_text(archived_rubric_path, "archived"),),
144
+ SelectionKind.BASE,
145
+ tmp_path,
146
+ (),
147
+ ((live_rubric_path, archived_rubric_path),),
148
+ )
149
+ runtime_set = _runtime_document_set(document_set)
150
+ assert runtime_set.documents == ()
151
+ assert runtime_set.deleted_paths == (live_rubric_path,)
152
+ assert runtime_set.renamed_paths == ()
@@ -209,7 +209,7 @@ def test_changed_line_filter_drops_other_located_line(tmp_path: Path) -> None:
209
209
  assert lint_report.diagnostics == ()
210
210
 
211
211
 
212
- def test_lint_keeps_located_findings_when_changed_lines_are_empty(
212
+ def test_lint_drops_located_findings_when_a_change_only_deletes_lines(
213
213
  tmp_path: Path,
214
214
  ) -> None:
215
215
  repository_root = tmp_path.resolve()
@@ -233,7 +233,7 @@ def test_lint_keeps_located_findings_when_changed_lines_are_empty(
233
233
  (document,),
234
234
  (_changed_document_rule("probe", collect_diagnostics),),
235
235
  )
236
- assert len(lint_report.diagnostics) == 1
236
+ assert lint_report.diagnostics == ()
237
237
 
238
238
 
239
239
  def test_code_rule_diagnostics_includes_comment_policy_without_prior_text(