claude-dev-env 1.92.0 → 1.93.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 (216) hide show
  1. package/CLAUDE.md +5 -39
  2. package/_shared/CLAUDE.md +2 -1
  3. package/_shared/advisor/CLAUDE.md +21 -0
  4. package/_shared/advisor/advisor-protocol.md +142 -0
  5. package/_shared/advisor/scripts/config/advisor_scripts_constants/__init__.py +1 -0
  6. package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +86 -0
  7. package/_shared/advisor/scripts/model_tier_run_validator.py +246 -0
  8. package/_shared/advisor/scripts/pyproject.toml +3 -0
  9. package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +302 -0
  10. package/_shared/advisor/scripts/tests/test_tier_model_ids.py +180 -0
  11. package/_shared/advisor/scripts/tier_model_ids.py +164 -0
  12. package/_shared/pr-loop/CLAUDE.md +1 -0
  13. package/_shared/pr-loop/precatch-rubric.md +65 -0
  14. package/_shared/pr-loop/scripts/CLAUDE.md +1 -1
  15. package/_shared/pr-loop/scripts/code_rules_gate.py +137 -5
  16. package/_shared/pr-loop/scripts/post_audit_thread.py +1 -1
  17. package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -1
  18. package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +16 -0
  19. package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviews_disabled_constants.py +2 -1
  20. package/_shared/pr-loop/scripts/reviewer_availability.py +42 -12
  21. package/_shared/pr-loop/scripts/reviews_disabled.py +65 -21
  22. package/_shared/pr-loop/scripts/terminology_sweep.py +69 -21
  23. package/_shared/pr-loop/scripts/tests/CLAUDE.md +1 -1
  24. package/_shared/pr-loop/scripts/tests/fixtures/{copilot_internal_user_jonecho.json → copilot_internal_user_example.json} +1 -1
  25. package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +1 -1
  26. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +146 -2
  27. package/_shared/pr-loop/scripts/tests/test_copilot_quota.py +8 -8
  28. package/_shared/pr-loop/scripts/tests/test_reviewer_availability.py +18 -5
  29. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +56 -5
  30. package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +104 -17
  31. package/agents/CLAUDE.md +3 -2
  32. package/agents/clasp-deployment-orchestrator.md +2 -2
  33. package/agents/code-advisor.md +7 -5
  34. package/agents/code-verifier.md +6 -2
  35. package/agents/session-advisor.md +27 -0
  36. package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +1 -1
  37. package/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md +1 -1
  38. package/audit-rubrics/prompts/category-a-api-contracts.md +2 -2
  39. package/audit-rubrics/prompts/category-b-selector-engine-compat.md +2 -2
  40. package/audit-rubrics/prompts/category-c-resource-cleanup.md +2 -2
  41. package/audit-rubrics/prompts/category-d-scoping-and-ordering.md +2 -2
  42. package/audit-rubrics/prompts/category-e-dead-code.md +2 -2
  43. package/audit-rubrics/prompts/category-f-silent-failures.md +2 -2
  44. package/audit-rubrics/prompts/category-g-bounds-and-overflow.md +2 -2
  45. package/audit-rubrics/prompts/category-h-security-boundaries.md +2 -2
  46. package/audit-rubrics/prompts/category-i-concurrency.md +2 -2
  47. package/audit-rubrics/prompts/category-j-code-rules-compliance.md +2 -2
  48. package/audit-rubrics/prompts/category-k-codebase-conflicts.md +2 -2
  49. package/audit-rubrics/prompts/category-l-behavior-equivalence.md +2 -2
  50. package/audit-rubrics/prompts/category-m-producer-consumer-cardinality.md +2 -2
  51. package/audit-rubrics/prompts/category-n-test-name-scenario-verifier.md +2 -2
  52. package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +2 -2
  53. package/audit-rubrics/prompts/category-p-name-vs-behavior-contract.md +2 -2
  54. package/bin/CLAUDE.md +1 -0
  55. package/bin/expand_home_directory_tokens.mjs +75 -0
  56. package/bin/install.mjs +21 -7
  57. package/bin/install.test.mjs +220 -10
  58. package/hooks/CLAUDE.md +1 -1
  59. package/hooks/blocking/CLAUDE.md +4 -1
  60. package/hooks/blocking/_path_setup.py +13 -0
  61. package/hooks/blocking/bash_pre_tool_use_dispatcher.py +281 -0
  62. package/hooks/blocking/code_rules_dead_module_constant.py +88 -16
  63. package/hooks/blocking/code_rules_enforcer.py +6 -0
  64. package/hooks/blocking/code_rules_imports_logging.py +34 -1
  65. package/hooks/blocking/code_rules_paths_syspath.py +1 -1
  66. package/hooks/blocking/code_rules_test_layout.py +272 -0
  67. package/hooks/blocking/nas_ssh_binary_enforcer.py +22 -12
  68. package/hooks/blocking/pii_prevention_blocker.py +781 -0
  69. package/hooks/blocking/pii_scanner.py +340 -0
  70. package/hooks/blocking/pre_tool_use_dispatcher.py +3 -3
  71. package/hooks/blocking/stop_dispatcher.py +84 -0
  72. package/hooks/blocking/test__path_setup.py +39 -0
  73. package/hooks/blocking/test_bash_dispatcher_interpreter_starts.py +61 -0
  74. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +294 -0
  75. package/hooks/blocking/test_code_rules_enforcer_cross_skill_duplicate.py +1 -1
  76. package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +51 -0
  77. package/hooks/blocking/test_code_rules_enforcer_dead_module_constant_read_cap.py +103 -0
  78. package/hooks/blocking/test_code_rules_enforcer_dot_test_pattern.py +1 -1
  79. package/hooks/blocking/test_code_rules_enforcer_file_global_constants.py +1 -1
  80. package/hooks/blocking/test_code_rules_enforcer_hardcoded_user_path.py +11 -11
  81. package/hooks/blocking/test_code_rules_enforcer_naive_datetime.py +1 -1
  82. package/hooks/blocking/test_code_rules_enforcer_same_file_inline_duplicate.py +1 -1
  83. package/hooks/blocking/test_code_rules_enforcer_test_layout.py +111 -0
  84. package/hooks/blocking/test_code_rules_enforcer_type_checking_scope.py +76 -0
  85. package/hooks/blocking/test_destructive_command_blocker.py +29 -9
  86. package/hooks/blocking/test_gh_body_arg_blocker.py +3 -3
  87. package/hooks/blocking/test_nas_ssh_binary_enforcer.py +96 -39
  88. package/hooks/blocking/test_pii_prevention_blocker.py +905 -0
  89. package/hooks/blocking/test_pii_prevention_windows_git_path.py +41 -0
  90. package/hooks/blocking/test_pii_scanner.py +165 -0
  91. package/hooks/blocking/test_pr_description_enforcer_pr_number.py +1 -1
  92. package/hooks/blocking/test_pre_tool_use_dispatcher.py +14 -13
  93. package/hooks/blocking/test_stop_dispatcher.py +187 -0
  94. package/hooks/blocking/test_verdict_directory_write_blocker.py +1 -1
  95. package/hooks/blocking/test_volatile_path_in_post_blocker.py +3 -3
  96. package/hooks/blocking/volatile_path_in_post_blocker.py +54 -5
  97. package/hooks/diagnostic/migrations/README.md +25 -26
  98. package/hooks/diagnostic/test_hook_log_extractor.py +10 -10
  99. package/hooks/hooks.json +10 -120
  100. package/hooks/hooks_constants/CLAUDE.md +12 -3
  101. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +78 -0
  102. package/hooks/hooks_constants/dead_module_constant_constants.py +1 -0
  103. package/hooks/hooks_constants/hosted_hook_runner.py +73 -0
  104. package/hooks/hooks_constants/local_identity.py +182 -0
  105. package/hooks/hooks_constants/nas_ssh_binary_enforcer_constants.py +4 -37
  106. package/hooks/hooks_constants/pii_prevention_constants.py +299 -0
  107. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
  108. package/hooks/hooks_constants/python_style_checks_constants.py +15 -0
  109. package/hooks/hooks_constants/stop_dispatcher_constants.py +26 -0
  110. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +87 -0
  111. package/hooks/hooks_constants/test_hardcoded_user_path_constants.py +2 -2
  112. package/hooks/hooks_constants/test_hosted_hook_runner.py +101 -0
  113. package/hooks/hooks_constants/test_layout_constants.py +28 -0
  114. package/hooks/hooks_constants/test_local_identity.py +88 -0
  115. package/hooks/hooks_constants/test_session_env_cleanup_constants.py +1 -1
  116. package/hooks/hooks_constants/test_stop_dispatcher_constants.py +32 -0
  117. package/hooks/validators/README.md +5 -6
  118. package/hooks/validators/__init__.py +2 -2
  119. package/hooks/validators/python_style_checks.py +298 -243
  120. package/hooks/validators/run_all_validators.py +6 -0
  121. package/hooks/validators/test_python_style_checks.py +278 -163
  122. package/package.json +2 -2
  123. package/rules/CLAUDE.md +7 -1
  124. package/rules/bdd.md +1 -1
  125. package/rules/nas-ssh-invocation.md +6 -4
  126. package/rules/no-justification-noise.md +61 -0
  127. package/rules/testing.md +0 -2
  128. package/scripts/test_setup_project_paths.py +1 -1
  129. package/skills/CLAUDE.md +6 -3
  130. package/skills/_shared/pr-loop/scripts/_path_resolver.py +2 -2
  131. package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +1 -1
  132. package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +1 -1
  133. package/skills/_shared/pr-loop/scripts/test__path_resolver.py +42 -3
  134. package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +11 -11
  135. package/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +26 -3
  136. package/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +49 -25
  137. package/skills/autoconverge/CLAUDE.md +3 -3
  138. package/skills/autoconverge/SKILL.md +113 -35
  139. package/skills/autoconverge/reference/CLAUDE.md +2 -2
  140. package/skills/autoconverge/reference/convergence.md +44 -22
  141. package/skills/autoconverge/reference/gotchas.md +11 -0
  142. package/skills/autoconverge/reference/stop-conditions.md +23 -13
  143. package/skills/autoconverge/workflow/CLAUDE.md +2 -1
  144. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +15 -19
  145. package/skills/autoconverge/workflow/converge.contract.test.mjs +43 -42
  146. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +141 -16
  147. package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +2 -2
  148. package/skills/autoconverge/workflow/converge.mjs +329 -68
  149. package/skills/autoconverge/workflow/converge.path-aware.test.mjs +1 -1
  150. package/skills/autoconverge/workflow/converge.precatch.test.mjs +152 -0
  151. package/skills/autoconverge/workflow/converge.run-input.test.mjs +3 -3
  152. package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +15 -15
  153. package/skills/autoconverge/workflow/test_convergence_summary.py +6 -6
  154. package/skills/bdd-protocol/SKILL.md +4 -5
  155. package/skills/bdd-protocol/references/anti-patterns.md +1 -1
  156. package/skills/bugteam/SKILL.md +8 -0
  157. package/skills/bugteam/reference/copilot-gap-analysis.md +20 -20
  158. package/skills/bugteam/reference/obstacles/audit-walk-categories.md +1 -1
  159. package/skills/bugteam/reference/team-setup.md +7 -5
  160. package/skills/bugteam/scripts/reflow_skill_md.py +1 -1
  161. package/skills/copilot-finding-triage/SKILL.md +124 -0
  162. package/skills/copilot-finding-triage/reference/tier-rubric.md +49 -0
  163. package/skills/copilot-finding-triage/scripts/conftest.py +8 -0
  164. package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/__init__.py +0 -0
  165. package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/config/__init__.py +0 -0
  166. package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/config/notify_ntfy_constants.py +32 -0
  167. package/skills/copilot-finding-triage/scripts/notify_ntfy.py +192 -0
  168. package/skills/copilot-finding-triage/scripts/test_notify_ntfy.py +141 -0
  169. package/skills/copilot-finding-triage/templates/notification.md +34 -0
  170. package/skills/copilot-review/SKILL.md +8 -6
  171. package/skills/findbugs/SKILL.md +4 -0
  172. package/skills/fixbugs/SKILL.md +8 -7
  173. package/skills/gotcha/CLAUDE.md +2 -2
  174. package/skills/gotcha/SKILL.md +4 -4
  175. package/skills/log-audit/SKILL.md +4 -6
  176. package/skills/monitor-open-prs/CLAUDE.md +1 -1
  177. package/skills/monitor-open-prs/SKILL.md +6 -1
  178. package/skills/orchestrator/SKILL.md +184 -0
  179. package/skills/orchestrator-refresh/SKILL.md +37 -0
  180. package/skills/post-audit-findings/SKILL.md +5 -9
  181. package/skills/pr-consistency-audit/SKILL.md +5 -1
  182. package/skills/pr-converge/CLAUDE.md +1 -1
  183. package/skills/pr-converge/SKILL.md +86 -47
  184. package/skills/pr-converge/reference/CLAUDE.md +1 -1
  185. package/skills/pr-converge/reference/convergence-gates.md +20 -17
  186. package/skills/pr-converge/reference/examples.md +63 -47
  187. package/skills/pr-converge/reference/fix-protocol.md +15 -15
  188. package/skills/pr-converge/reference/ground-rules.md +11 -7
  189. package/skills/pr-converge/reference/multi-pr-orchestration.md +6 -5
  190. package/skills/pr-converge/reference/per-tick.md +129 -107
  191. package/skills/pr-converge/reference/state-schema.md +15 -10
  192. package/skills/pr-converge/scripts/check_convergence.py +5 -4
  193. package/skills/pr-converge/scripts/test_check_convergence.py +6 -4
  194. package/skills/pr-fix-protocol/SKILL.md +3 -8
  195. package/skills/pr-loop-cloud-transport/SKILL.md +102 -0
  196. package/skills/pr-loop-cloud-transport/reference/identity-and-hooks.md +40 -0
  197. package/skills/pr-loop-cloud-transport/reference/substitution-matrix.md +48 -0
  198. package/skills/pr-loop-lifecycle/SKILL.md +5 -9
  199. package/skills/pr-scope-resolve/SKILL.md +3 -6
  200. package/skills/privacy-hygiene/SKILL.md +114 -0
  201. package/skills/qbug/SKILL.md +8 -8
  202. package/skills/rebase/SKILL.md +5 -1
  203. package/skills/refine/SKILL.md +4 -5
  204. package/skills/reviewer-gates/SKILL.md +7 -11
  205. package/skills/session-log/SKILL.md +4 -1
  206. package/skills/skill-builder/SKILL.md +3 -6
  207. package/skills/structure-prompt/SKILL.md +4 -5
  208. package/skills/team-advisor/SKILL.md +32 -164
  209. package/skills/test_markdown_link_integrity.py +10 -6
  210. package/skills/update/SKILL.md +5 -1
  211. package/skills/usage-pause/SKILL.md +14 -5
  212. package/skills/usage-pause/scripts/resolve_usage_window.py +83 -5
  213. package/skills/usage-pause/scripts/test_resolve_usage_window.py +185 -17
  214. package/skills/usage-pause/scripts/usage_pause_constants/resolve_usage_window_constants.py +4 -3
  215. package/skills/verified-build/SKILL.md +4 -9
  216. package/skills/team-advisor-refresh/SKILL.md +0 -25
@@ -39,7 +39,7 @@ test('a path-scoped run defers to a step that names a different repository direc
39
39
  test('convergeAgent prepends the worktree directive for the active repo path', () => {
40
40
  const agentDefinition = sliceBetween('const convergeAgent =', '\nconst PRE_COMMIT_GATE_STEP');
41
41
  assert.match(agentDefinition, /worktreeDirective\(activeRepoPath\)/);
42
- assert.match(agentDefinition, /HEADLESS_SAFETY_PREAMBLE/);
42
+ assert.match(agentDefinition, /HEADLESS_EDIT_PREAMBLE/);
43
43
  });
44
44
 
45
45
  test('the run binds activeRepoPath from input.repoPath after the input is parsed', () => {
@@ -0,0 +1,152 @@
1
+ import { test } from 'node:test';
2
+ import { strict as assert } from 'node:assert';
3
+ import { readFileSync } from 'node:fs';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { dirname, join } from 'node:path';
6
+
7
+ const workflowDirectory = dirname(fileURLToPath(import.meta.url));
8
+ const convergeSource = readFileSync(join(workflowDirectory, 'converge.mjs'), 'utf8');
9
+
10
+ function functionSource(functionName) {
11
+ const functionStart = convergeSource.indexOf(`function ${functionName}(`);
12
+ assert.notEqual(functionStart, -1, `expected ${functionName} to exist`);
13
+ const nextMatch = /\n(?:async )?function /.exec(convergeSource.slice(functionStart + 1));
14
+ const functionEnd =
15
+ nextMatch === null ? convergeSource.length : functionStart + 1 + nextMatch.index;
16
+ return convergeSource.slice(functionStart, functionEnd);
17
+ }
18
+
19
+ function sliceBetween(startNeedle, endNeedle) {
20
+ const sliceStart = convergeSource.indexOf(startNeedle);
21
+ assert.notEqual(sliceStart, -1, `expected ${startNeedle} to exist`);
22
+ const sliceEnd = convergeSource.indexOf(endNeedle, sliceStart + startNeedle.length);
23
+ assert.notEqual(sliceEnd, -1, `expected ${endNeedle} to exist after ${startNeedle}`);
24
+ return convergeSource.slice(sliceStart, sliceEnd);
25
+ }
26
+
27
+ test('runStaticSweep exists and runs after the preflight-git no-SHA guard and before the parallel lenses', () => {
28
+ assert.match(convergeSource, /function runStaticSweep\(/, 'expected the static-sweep lens builder to exist');
29
+ const sweepCallIndex = convergeSource.indexOf('await runStaticSweep(');
30
+ assert.notEqual(sweepCallIndex, -1, 'expected the CONVERGE round to invoke runStaticSweep');
31
+ const noShaGuardIndex = convergeSource.indexOf('preflight-git agent returned no SHA');
32
+ const parallelIndex = convergeSource.indexOf('const lenses = await parallel(');
33
+ assert.ok(
34
+ noShaGuardIndex !== -1 && noShaGuardIndex < sweepCallIndex,
35
+ 'expected the static sweep to run after the no-SHA guard',
36
+ );
37
+ assert.ok(
38
+ sweepCallIndex < parallelIndex,
39
+ 'expected the static sweep to run before the opus reading lenses so they review sweep-clean code',
40
+ );
41
+ });
42
+
43
+ test('the static-sweep prompt names the deterministic gates and never invokes the gh CLI', () => {
44
+ const body = functionSource('runStaticSweep');
45
+ assert.match(body, /code_rules_gate\.py/, 'expected the CODE_RULES gate to be named');
46
+ assert.match(body, /--base/, 'expected the gate to run against the origin/main base');
47
+ assert.match(body, /\bruff\b/, 'expected ruff to be named');
48
+ assert.match(body, /\bmypy\b/, 'expected mypy to be named');
49
+ assert.match(body, /\bpytest\b/, 'expected stem-matched pytest to be named');
50
+ assert.doesNotMatch(body, /\bgh\b/, 'expected the sweep to use only local commands so it runs in a cloud session');
51
+ });
52
+
53
+ test('the static-sweep runs on the sonnetMedium tier via convergeReadOnlyAgent with the Explore agent type', () => {
54
+ const body = functionSource('runStaticSweep');
55
+ assert.match(body, /convergeReadOnlyAgent\(/, 'expected the sweep to spawn through the read-only preamble');
56
+ assert.match(body, /renderLensDiffContext\(preflightResult\)/, 'expected the sweep to inject the preflight changed-file context');
57
+ assert.match(body, /label: 'lens:static-sweep'/);
58
+ assert.match(body, /agentType: 'Explore'/);
59
+ assert.match(body, /\.\.\.TIERS\.sonnetMedium/, 'expected the deterministic sweep to run on the cheaper sonnet tier');
60
+ });
61
+
62
+ test('a static-sweep finding routes through applyFixes with the static-sweep label and nulls head before continue', () => {
63
+ const sweepBranch = sliceBetween('await runStaticSweep(', "const lenses = await parallel(");
64
+ assert.match(sweepBranch, /applyFixes\(head, sweep\.findings, 'static-sweep'\)/, 'expected sweep findings to route through applyFixes');
65
+ assert.match(sweepBranch, /detectFixProgress\(sweepFix, head, false\)/, 'expected the sweep fix to check fix progress with no thread-bearing finding');
66
+ const headNullIndex = sweepBranch.indexOf('head = null');
67
+ const continueIndex = sweepBranch.indexOf('continue');
68
+ assert.notEqual(headNullIndex, -1, 'expected the sweep-findings branch to null head so the next round reviews the fixed code');
69
+ assert.ok(headNullIndex < continueIndex, 'expected head to be invalidated before the sweep branch continues');
70
+ });
71
+
72
+ test('runAuditLens carries the adversarial second pass and points at the pre-catch rubric', () => {
73
+ const body = functionSource('runAuditLens');
74
+ assert.match(body, /missed at least 3 P1/, 'expected the mandatory adversarial second pass');
75
+ assert.match(body, /CONFIG\.precatchRubric/, 'expected the audit lens to point at the pre-catch rubric for the doc-parity/test/PR-description lanes');
76
+ assert.doesNotMatch(body, /nothing new is an acceptable/i, 'expected a bare "nothing new" to be rejected');
77
+ });
78
+
79
+ test('runSelfReviewLens exists on opusMedium via convergeReadOnlyAgent and covers the three semantic lanes', () => {
80
+ const body = functionSource('runSelfReviewLens');
81
+ assert.match(body, /convergeReadOnlyAgent\(/, 'expected the self-review lens to spawn through the read-only preamble');
82
+ assert.match(body, /renderLensDiffContext\(preflightResult\)/, 'expected the self-review lens to inject the preflight changed-file context');
83
+ assert.match(body, /CONFIG\.precatchRubric/, 'expected the self-review lens to read the pre-catch rubric');
84
+ assert.match(body, /pr-consistency-audit/, 'expected the self-review lens to reuse the pr-consistency-audit method');
85
+ assert.match(body, /category-o-docstring-vs-impl-drift/, 'expected the self-review lens to cite the category-o drift rubric');
86
+ assert.match(body, /parity/i, 'expected the doc-vs-code parity lane');
87
+ assert.match(body, /Test-assertion completeness/i, 'expected the test-assertion completeness lane');
88
+ assert.match(body, /PR-description-vs-diff/i, 'expected the PR-description parity lane');
89
+ assert.match(body, /label: 'lens:self-review'/);
90
+ assert.match(body, /\.\.\.TIERS\.opusMedium/, 'expected the semantic lens to stay on opus');
91
+ });
92
+
93
+ test('the round parallel array holds exactly the three internal lenses and no Bugbot slot', () => {
94
+ const parallelArray = sliceBetween('const lenses = await parallel([', '])');
95
+ assert.match(parallelArray, /runCodeReviewLens\(head, reviewerAvailability\)/);
96
+ assert.match(parallelArray, /runAuditLens\(head, reviewerAvailability\)/);
97
+ assert.match(parallelArray, /runSelfReviewLens\(head, reviewerAvailability\)/);
98
+ assert.doesNotMatch(parallelArray, /runBugbotLens/, 'expected Bugbot to leave the per-round parallel and become a terminal gate');
99
+ assert.doesNotMatch(parallelArray, /isBugbotDownPreSpawn/, 'expected the pre-spawn Bugbot-down decision to be gone from the round');
100
+ assert.doesNotMatch(convergeSource, /const isBugbotDownPreSpawn/, 'expected no pre-spawn Bugbot-down decision anywhere in the round');
101
+ });
102
+
103
+ test('both the CONVERGE clean branch and the standards-only branch route to the terminal BUGBOT phase', () => {
104
+ const convergeBranch = sliceBetween("if (phase === 'CONVERGE') {", "if (phase === 'BUGBOT') {");
105
+ const toBugbot = convergeBranch.match(/phase = 'BUGBOT'/g) || [];
106
+ assert.equal(toBugbot.length, 2, 'expected the standards-only and all-clean branches to both set phase = BUGBOT');
107
+ assert.doesNotMatch(convergeBranch, /phase = 'COPILOT'/, 'expected the CONVERGE block to route to BUGBOT, not straight to COPILOT');
108
+ });
109
+
110
+ test('the terminal BUGBOT phase skips runBugbotLens when the reviewer-availability gate reports Bugbot down', () => {
111
+ const bugbotPhase = sliceBetween("if (phase === 'BUGBOT') {", "if (phase === 'COPILOT') {");
112
+ const availabilityGateIndex = bugbotPhase.indexOf('resolveReviewerDown(reviewerAvailability?.bugbot, input.bugbotDisabled || false)');
113
+ const gateCallIndex = bugbotPhase.indexOf('await runBugbotLens(head, reviewerAvailability)');
114
+ assert.notEqual(availabilityGateIndex, -1, 'expected the terminal gate to consult resolveReviewerDown before spawning the Bugbot lens');
115
+ assert.notEqual(gateCallIndex, -1, 'expected the terminal gate to call runBugbotLens when Bugbot is enabled');
116
+ assert.ok(availabilityGateIndex < gateCallIndex, 'expected the availability gate to short-circuit before any Bugbot agent spawns');
117
+ const beforeGate = bugbotPhase.slice(availabilityGateIndex, gateCallIndex);
118
+ assert.match(beforeGate, /bugbotDown = true/, 'expected the availability-down path to mark bugbotDown');
119
+ assert.match(beforeGate, /phase = 'COPILOT'/, 'expected the availability-down path to advance to the Copilot gate with no agent');
120
+ });
121
+
122
+ test('the terminal BUGBOT phase classifies via the shared reviewer-gate classifier and routes each outcome', () => {
123
+ const bugbotPhase = sliceBetween("if (phase === 'BUGBOT') {", "if (phase === 'COPILOT') {");
124
+ assert.match(bugbotPhase, /classifyReviewerGateOutcome\(bugbot\)/, 'expected the terminal gate to reuse the shared reviewer-gate classifier');
125
+ assert.match(bugbotPhase, /applyFixes\(head, bugbotOutcome\.findings, 'bugbot'\)/, 'expected a Bugbot fix outcome to route through applyFixes');
126
+ assert.match(bugbotPhase, /head = null\n\s*phase = 'CONVERGE'/, 'expected a Bugbot fix to invalidate head and re-enter CONVERGE');
127
+ assert.match(bugbotPhase, /phase = 'COPILOT'/, 'expected a clean or down Bugbot verdict to advance to the Copilot gate');
128
+ });
129
+
130
+ test('convergeReadOnlyAgent exists and the read-only preamble omits the rm-shape paragraph the edit preamble keeps', () => {
131
+ assert.match(convergeSource, /const convergeReadOnlyAgent = \(prompt, options\) =>/, 'expected the read-only agent spawner to exist');
132
+ const preambleRegion = convergeSource.slice(
133
+ convergeSource.indexOf('const HEADLESS_EDIT_PREAMBLE ='),
134
+ convergeSource.indexOf('\nlet activeRepoPath'),
135
+ );
136
+ const preambleModule = new Function(
137
+ `${preambleRegion}\nreturn { HEADLESS_EDIT_PREAMBLE, HEADLESS_READONLY_PREAMBLE };`,
138
+ )();
139
+ assert.match(preambleModule.HEADLESS_EDIT_PREAMBLE, /rm shape rules/, 'expected the edit preamble to retain the rm-shape rules');
140
+ assert.doesNotMatch(preambleModule.HEADLESS_READONLY_PREAMBLE, /rm shape rules/, 'expected the read-only preamble to drop the rm-shape paragraph');
141
+ assert.ok(
142
+ preambleModule.HEADLESS_READONLY_PREAMBLE.length < preambleModule.HEADLESS_EDIT_PREAMBLE.length,
143
+ 'expected the read-only preamble to be shorter than the edit preamble',
144
+ );
145
+ });
146
+
147
+ test('COMMIT_RECOVERY_MAX_ATTEMPTS is one and bounds the commitWithRecovery loop', () => {
148
+ assert.match(convergeSource, /const COMMIT_RECOVERY_MAX_ATTEMPTS = 1/, 'expected a dedicated commit-recovery cap of one');
149
+ assert.match(convergeSource, /const FIX_RECOVERY_MAX_ATTEMPTS = 2/, 'expected the wider verify-recovery cap to stay at two');
150
+ const commitBody = functionSource('commitWithRecovery');
151
+ assert.match(commitBody, /attempt < COMMIT_RECOVERY_MAX_ATTEMPTS/, 'expected commitWithRecovery to bound its loop by the dedicated cap');
152
+ });
@@ -22,7 +22,7 @@ const productionModule = new Function(
22
22
 
23
23
  const { normalizeRunInput, classifyRunInput } = productionModule;
24
24
 
25
- const VALID_COORDINATES = { owner: 'jl-cmd', repo: 'claude-code-config', prNumber: 543 };
25
+ const VALID_COORDINATES = { owner: 'jl-cmd', repo: 'claude-dev-env', prNumber: 543 };
26
26
 
27
27
  test('an object payload passes through unchanged', () => {
28
28
  assert.deepEqual(normalizeRunInput(VALID_COORDINATES), VALID_COORDINATES);
@@ -59,13 +59,13 @@ test('classifyRunInput blocks a null payload with a structured blocker', () => {
59
59
  });
60
60
 
61
61
  test('classifyRunInput blocks a payload missing owner', () => {
62
- const classified = classifyRunInput({ repo: 'claude-code-config', prNumber: 543 });
62
+ const classified = classifyRunInput({ repo: 'claude-dev-env', prNumber: 543 });
63
63
  assert.equal(classified.input, null);
64
64
  assert.match(classified.blocker, /coordinates/i);
65
65
  });
66
66
 
67
67
  test('classifyRunInput blocks a payload missing prNumber', () => {
68
- const classified = classifyRunInput({ owner: 'jl-cmd', repo: 'claude-code-config' });
68
+ const classified = classifyRunInput({ owner: 'jl-cmd', repo: 'claude-dev-env' });
69
69
  assert.equal(classified.input, null);
70
70
  assert.match(classified.blocker, /coordinates/i);
71
71
  });
@@ -38,15 +38,15 @@ const SCRIPT_PATH = '/abs/skills/autoconverge/workflow/converge.mjs';
38
38
 
39
39
  function validEntry(prNumber) {
40
40
  return {
41
- owner: 'JonEcho',
42
- repo: 'python-automation',
41
+ owner: 'example-org',
42
+ repo: 'example-repo',
43
43
  prNumber,
44
44
  repoPath: `/worktrees/pr-${prNumber}`,
45
45
  };
46
46
  }
47
47
 
48
48
  function validArgs() {
49
- return { convergeScriptPath: SCRIPT_PATH, prs: [validEntry(398), validEntry(402)] };
49
+ return { convergeScriptPath: SCRIPT_PATH, prs: [validEntry(101), validEntry(102)] };
50
50
  }
51
51
 
52
52
  test('an object payload passes through unchanged', () => {
@@ -69,7 +69,7 @@ test('valid coordinates classify with no blocker and keep every PR entry', () =>
69
69
  });
70
70
 
71
71
  test('a missing convergeScriptPath is blocked', () => {
72
- const classified = classifyMultiInput({ prs: [validEntry(398)] });
72
+ const classified = classifyMultiInput({ prs: [validEntry(101)] });
73
73
  assert.equal(classified.input, null);
74
74
  assert.match(classified.blocker, /convergeScriptPath/);
75
75
  });
@@ -93,14 +93,14 @@ test('a non-array prs value is blocked', () => {
93
93
  });
94
94
 
95
95
  test('an entry missing prNumber is blocked', () => {
96
- const badEntry = { owner: 'JonEcho', repo: 'python-automation', repoPath: '/w/x' };
96
+ const badEntry = { owner: 'example-org', repo: 'example-repo', repoPath: '/w/x' };
97
97
  const classified = classifyMultiInput({ convergeScriptPath: SCRIPT_PATH, prs: [badEntry] });
98
98
  assert.equal(classified.input, null);
99
99
  assert.match(classified.blocker, /missing/);
100
100
  });
101
101
 
102
102
  test('an entry missing repoPath is blocked', () => {
103
- const badEntry = { owner: 'JonEcho', repo: 'python-automation', prNumber: 398 };
103
+ const badEntry = { owner: 'example-org', repo: 'example-repo', prNumber: 101 };
104
104
  const classified = classifyMultiInput({ convergeScriptPath: SCRIPT_PATH, prs: [badEntry] });
105
105
  assert.equal(classified.input, null);
106
106
  assert.match(classified.blocker, /missing/);
@@ -113,29 +113,29 @@ test('a null payload is blocked', () => {
113
113
  });
114
114
 
115
115
  test('childRunInput forwards the PR coordinates to the child run', () => {
116
- const childArgs = childRunInput(validEntry(398));
117
- assert.equal(childArgs.owner, 'JonEcho');
118
- assert.equal(childArgs.repo, 'python-automation');
119
- assert.equal(childArgs.prNumber, 398);
120
- assert.equal(childArgs.repoPath, '/worktrees/pr-398');
116
+ const childArgs = childRunInput(validEntry(101));
117
+ assert.equal(childArgs.owner, 'example-org');
118
+ assert.equal(childArgs.repo, 'example-repo');
119
+ assert.equal(childArgs.prNumber, 101);
120
+ assert.equal(childArgs.repoPath, '/worktrees/pr-101');
121
121
  });
122
122
 
123
123
  test('childRunInput forwards copilotDisabled true when the entry opts out', () => {
124
- const optedOutEntry = { ...validEntry(398), copilotDisabled: true };
124
+ const optedOutEntry = { ...validEntry(101), copilotDisabled: true };
125
125
  assert.equal(childRunInput(optedOutEntry).copilotDisabled, true);
126
126
  });
127
127
 
128
128
  test('childRunInput defaults copilotDisabled to false when the entry omits it', () => {
129
- assert.equal(childRunInput(validEntry(398)).copilotDisabled, false);
129
+ assert.equal(childRunInput(validEntry(101)).copilotDisabled, false);
130
130
  });
131
131
 
132
132
  test('childRunInput forwards bugbotDisabled true when the entry opts out', () => {
133
- const optedOutEntry = { ...validEntry(398), bugbotDisabled: true };
133
+ const optedOutEntry = { ...validEntry(101), bugbotDisabled: true };
134
134
  assert.equal(childRunInput(optedOutEntry).bugbotDisabled, true);
135
135
  });
136
136
 
137
137
  test('childRunInput defaults bugbotDisabled to false when the entry omits it', () => {
138
- assert.equal(childRunInput(validEntry(398)).bugbotDisabled, false);
138
+ assert.equal(childRunInput(validEntry(101)).bugbotDisabled, false);
139
139
  });
140
140
 
141
141
  test('the malformed-input blocker return carries an empty allDeferredPrs list', async () => {
@@ -32,7 +32,7 @@ def test_prompt_carries_pr_coordinates_and_round_count() -> None:
32
32
  """Should name the PR coordinates and the aggregated round count."""
33
33
  prompt = convergence_summary.build_summary_prompt(
34
34
  owner="jl-cmd",
35
- repo="claude-code-config",
35
+ repo="claude-dev-env",
36
36
  pr_number=581,
37
37
  round_count=39,
38
38
  findings=SAMPLE_FINDINGS,
@@ -41,17 +41,17 @@ def test_prompt_carries_pr_coordinates_and_round_count() -> None:
41
41
  copilot_note=None,
42
42
  )
43
43
 
44
- assert "owner=jl-cmd repo=claude-code-config PR #581" in prompt
45
- assert "https://github.com/jl-cmd/claude-code-config/pull/581" in prompt
44
+ assert "owner=jl-cmd repo=claude-dev-env PR #581" in prompt
45
+ assert "https://github.com/jl-cmd/claude-dev-env/pull/581" in prompt
46
46
  assert "convergence in 39 round(s)" in prompt
47
- assert "gh api repos/jl-cmd/claude-code-config/pulls/581" in prompt
47
+ assert "gh api repos/jl-cmd/claude-dev-env/pulls/581" in prompt
48
48
 
49
49
 
50
50
  def test_prompt_instructs_plain_json_object_not_structured_output() -> None:
51
51
  """Should instruct a plain JSON object answer and never name the StructuredOutput tool."""
52
52
  prompt = convergence_summary.build_summary_prompt(
53
53
  owner="jl-cmd",
54
- repo="claude-code-config",
54
+ repo="claude-dev-env",
55
55
  pr_number=581,
56
56
  round_count=39,
57
57
  findings=SAMPLE_FINDINGS,
@@ -68,7 +68,7 @@ def test_prompt_numbers_every_finding_with_severity_and_location() -> None:
68
68
  """Should list each aggregated finding numbered with severity, file, and line."""
69
69
  prompt = convergence_summary.build_summary_prompt(
70
70
  owner="jl-cmd",
71
- repo="claude-code-config",
71
+ repo="claude-dev-env",
72
72
  pr_number=581,
73
73
  round_count=39,
74
74
  findings=SAMPLE_FINDINGS,
@@ -1,11 +1,10 @@
1
1
  ---
2
2
  name: bdd-protocol
3
3
  description: >-
4
- On-demand BDD depth aligned with jl-cmd/claude-code-config#82: Example Mapping (Smart &
5
- Molak §6.4), scenario quality and anti-patterns (§7.6), solo minimal BDD, outside-in test
6
- layout. Use when expanding `<behavior_protocol>` in the system prompt, writing executable
7
- specifications, or when the user asks for discovery, "the one where" examples, or BDD
8
- scenario quality. Triggers: bdd-protocol, Example Mapping, BDD anti-patterns, §7.6.
4
+ On-demand BDD depth: Example Mapping, scenario quality and anti-patterns, solo
5
+ minimal BDD, and outside-in test layout. Use when expanding `<behavior_protocol>`,
6
+ writing executable specifications, or giving 'the one where' examples.
7
+ Triggers: bdd-protocol, Example Mapping, BDD anti-patterns, §7.6.
9
8
  ---
10
9
  @~/.claude/skills/bdd-protocol/references/example-mapping.md
11
10
  @~/.claude/skills/bdd-protocol/references/anti-patterns.md
@@ -1,6 +1,6 @@
1
1
  # Scenario quality and anti-patterns (§7.6 and related)
2
2
 
3
- Use this as a **checklist** when writing or reviewing scenarios and developer-facing specifications. Phrasing follows *BDD in Action* 2e and the catalog summarized in [claude-code-config#82](https://github.com/jl-cmd/claude-code-config/issues/82).
3
+ Use this as a **checklist** when writing or reviewing scenarios and developer-facing specifications. Phrasing follows *BDD in Action* 2e and the catalog summarized in [claude-dev-env#82](https://github.com/jl-cmd/claude-dev-env/issues/82).
4
4
 
5
5
  ## Positive targets (what "good" looks like)
6
6
 
@@ -18,6 +18,14 @@ The audit agent loads the A–P category rubrics from
18
18
  `$HOME/.claude/audit-rubrics/{category_rubrics,prompts}/` alongside
19
19
  [`PROMPTS.md`](PROMPTS.md) and produces a single outcome XML per loop.
20
20
 
21
+ ## Transport check (before any GitHub step)
22
+
23
+ Run `command -v gh`; when it succeeds, run `gh auth status`; once the PR
24
+ scope is resolved, run `gh api repos/<owner>/<repo> --jq .permissions.push`
25
+ and take `true` as the pass. When any check fails, run the
26
+ `pr-loop-cloud-transport` skill first and route every `gh` operation in this
27
+ skill through its substitution matrix.
28
+
21
29
  ## Pre-flight
22
30
 
23
31
  ```bash
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **Status: HISTORICAL — patch plan partially superseded.**
4
4
  >
5
- > This file documents the read-only investigation that motivated the K–N rubric addendum and the Step 2.6 / Step 3.5 standards-review phases. **The audit-rubric portion of the patch plan was reverted in [PR #292](https://github.com/jl-cmd/claude-code-config/pull/292):** PROMPTS.md no longer carries the K–N addendum or the `<copilot_derived_addendum_source>` block, and SKILL.md no longer runs the Step 2.6 / Step 3.5 standards-review phases.
5
+ > This file documents the read-only investigation that motivated the K–N rubric addendum and the Step 2.6 / Step 3.5 standards-review phases. **The audit-rubric portion of the patch plan was reverted in [PR #292](https://github.com/jl-cmd/claude-dev-env/pull/292):** PROMPTS.md no longer carries the K–N addendum or the `<copilot_derived_addendum_source>` block, and SKILL.md no longer runs the Step 2.6 / Step 3.5 standards-review phases.
6
6
  >
7
7
  > **What replaced K–N (partial coverage):** several judgment-heavy categories now have deterministic validators in `code_rules_enforcer.py` — `check_collection_prefix` (category K), `check_library_print` (category L), `check_inline_literal_collections`, `check_loop_variable_naming`, `check_parameter_annotations`, `check_return_annotations`. These validators are exercised by `bugteam_code_rules_gate.py` during the bugteam pre-audit / loop, and by the shipped repo `pre-commit` / `pre-push` git hooks that invoke that gate. They are **not** wired into the repo's default `PreToolUse` Write|Edit hook list in `packages/claude-dev-env/hooks/hooks.json`, so they do not block at Write/Edit time by default — enforcement comes from the gate (audit-time) and the git hooks (commit/push-time).
8
8
  >
@@ -12,7 +12,7 @@
12
12
 
13
13
  This file is the reference record produced by the read-only investigation of why the `/bugteam` audit/fix loop and `bugteam_code_rules_gate.py` repeatedly miss the classes of code-quality violations that the GitHub Copilot reviewer raises on follow-up review rounds. It is written so future bugteam runs can skim the inventory, the rubric/validator coverage diffs, and the patch plan without re-deriving them.
14
14
 
15
- Sources of truth cited below: `~/.claude/docs/CODE_RULES.md`, `~/.claude/CLAUDE.md`, `~/.claude/rules/file-global-constants.md`, `~/.claude/skills/bugteam/SKILL.md`, `~/.claude/skills/bugteam/PROMPTS.md`, `~/.claude/skills/bugteam/CONSTRAINTS.md`, `~/.claude/skills/bugteam/scripts/bugteam_code_rules_gate.py`, `~/.claude/skills/bugteam/scripts/bugteam_preflight.py`, `~/.claude/hooks/blocking/code_rules_enforcer.py`, plus `gh api repos/JonEcho/python-automation/pulls/{70,73}/comments` filtered to author `Copilot`.
15
+ Sources of truth cited below: `~/.claude/docs/CODE_RULES.md`, `~/.claude/CLAUDE.md`, `~/.claude/rules/file-global-constants.md`, `~/.claude/skills/bugteam/SKILL.md`, `~/.claude/skills/bugteam/PROMPTS.md`, `~/.claude/skills/bugteam/CONSTRAINTS.md`, `~/.claude/skills/bugteam/scripts/bugteam_code_rules_gate.py`, `~/.claude/skills/bugteam/scripts/bugteam_preflight.py`, `~/.claude/hooks/blocking/code_rules_enforcer.py`, plus `gh api repos/example-org/example-repo/pulls/{101,102}/comments` filtered to author `Copilot`.
16
16
 
17
17
  ---
18
18
 
@@ -23,22 +23,22 @@ Sources of truth cited below: `~/.claude/docs/CODE_RULES.md`, `~/.claude/CLAUDE.
23
23
  Copilot review comments fetched with:
24
24
 
25
25
  ```
26
- gh api repos/JonEcho/python-automation/pulls/70/comments --paginate --jq '.[] | select(.user.login == "Copilot")'
27
- gh api repos/JonEcho/python-automation/pulls/73/comments --paginate --jq '.[] | select(.user.login == "Copilot")'
26
+ gh api repos/example-org/example-repo/pulls/101/comments --paginate --jq '.[] | select(.user.login == "Copilot")'
27
+ gh api repos/example-org/example-repo/pulls/102/comments --paginate --jq '.[] | select(.user.login == "Copilot")'
28
28
  ```
29
29
 
30
- PR #70 head SHA `29117309cf4ec1e83883160d8c819e0843f9c3ac` (merged). PR #73 head SHA `c9c935a96cc59d39d623dc7eddda3d341007607c` (merged); Copilot reviewed at original commit `e4abf52c3a6c724b4e64bfed0d979cd60a2c8bf0`.
30
+ PR #101 head SHA `29117309cf4ec1e83883160d8c819e0843f9c3ac` (merged). PR #102 head SHA `c9c935a96cc59d39d623dc7eddda3d341007607c` (merged); Copilot reviewed at original commit `e4abf52c3a6c724b4e64bfed0d979cd60a2c8bf0`.
31
31
 
32
32
  | finding_id | pr_number | file:line | rule_cited | severity | mapped_bugteam_category_letter | layer_that_should_have_caught_it |
33
33
  |---|---|---|---|---|---|---|
34
- | 3153098661 | 70 | `shared_utils/theme_db/writer.py:158` | Magic values — column-name string literals (`"theme_name"`, `"content_id"`, …) hardcoded inside SQL builder bodies (CODE_RULES.md §⚡ Magic values; J) | P1 | J | bugteam pre-flight gate (`bugteam_code_rules_gate.py`) — string literals are masked by `_mask_string_literals_preserving_length`, so the number-only magic-value detector never sees them; bugteam audit rubric (J) names the rule but the regex passes |
35
- | 3153098689 | 70 | `shared_utils/theme_db/writer.py:361` | File-length / function-length / SRP smell (`write_outcome` >30 lines, module 446 lines exceeds the 400-line advisory) | P2 | none | initial+final standards-review phases bracketing the loop — no rubric letter exists; the existing 400/1000-line advisory in `code_rules_enforcer.advise_file_line_count` is stderr-only and never blocks |
36
- | 3153098727 | 70 | `shared_utils/theme_db/summary.py:267` | Library `print()` calls in non-CLI library code (CODE_RULES.md §Self-Documenting Code; "Make output stream explicit" practice) | P1 | none | harness PreToolUse hook (`code_rules_enforcer.py`) — no detector exists for `print(`/`sys.stdout.write` inside library modules |
37
- | 3153098762 | 70 | `shared_utils/theme_db/summary.py:263` | PR-description spec drift — banner missing column-header rows promised in PR body | P2 | A (loosely) | initial+final standards-review phases — A is signature/async-shaped, not "promised behavior vs implementation" shaped |
38
- | 3153098782 | 70 | `shared_utils/theme_db/writer.py:125` | Naming clarity — `_is_set_column_value` reads like it excludes `None` but does not | P2 | none | bugteam audit rubric addendum — no naming-clarity category in A–J |
39
- | 3153475246 | 73 | `shared_utils/theme_db/config/constants.py:91` | Collection naming — `THEMES_INSERT_REQUIRED_COLUMN_NAMES` is a tuple and must use the `ALL_*` prefix (CODE_RULES.md §5 "Extended naming rules" → Collections: `all_orders`, `all_users`) | P1 | none | harness PreToolUse hook AND bugteam pre-flight gate — no detector for the collection-prefix rule. Reproduced at `e4abf52c`; renamed to `ALL_THEMES_INSERT_REQUIRED_COLUMN_NAMES` in the merged PR head |
40
- | 3153475297 | 73 | `shared_utils/theme_db/writer.py:296` | Collection naming — parameter `column_value_pairs` is a list and must use the `all_*` prefix | P1 | none | harness PreToolUse hook AND bugteam pre-flight gate — same gap as 3153475246 for parameter names |
41
- | 3153475331 | 73 | `shared_utils/theme_db/summary.py:206` (referenced; underlying defect is in `shared_utils/theme_db/tracker.py` `flush()`) | Wrapper plumb-through — public `tracker.flush(*, output_folder)` silently drops `loud_banner_stream` that `ThemeDatabaseWriteSummary.flush(*, output_folder, loud_banner_stream=None)` accepts | P1 | A (loosely) | bugteam audit rubric addendum — A focuses on signatures/return types, not on whether a wrapper preserves the optional kwargs of the function it delegates to |
34
+ | 3153098661 | 101 | `shared_utils/theme_db/writer.py:158` | Magic values — column-name string literals (`"theme_name"`, `"content_id"`, …) hardcoded inside SQL builder bodies (CODE_RULES.md §⚡ Magic values; J) | P1 | J | bugteam pre-flight gate (`bugteam_code_rules_gate.py`) — string literals are masked by `_mask_string_literals_preserving_length`, so the number-only magic-value detector never sees them; bugteam audit rubric (J) names the rule but the regex passes |
35
+ | 3153098689 | 101 | `shared_utils/theme_db/writer.py:361` | File-length / function-length / SRP smell (`write_outcome` >30 lines, module 446 lines exceeds the 400-line advisory) | P2 | none | initial+final standards-review phases bracketing the loop — no rubric letter exists; the existing 400/1000-line advisory in `code_rules_enforcer.advise_file_line_count` is stderr-only and never blocks |
36
+ | 3153098727 | 101 | `shared_utils/theme_db/summary.py:267` | Library `print()` calls in non-CLI library code (CODE_RULES.md §Self-Documenting Code; "Make output stream explicit" practice) | P1 | none | harness PreToolUse hook (`code_rules_enforcer.py`) — no detector exists for `print(`/`sys.stdout.write` inside library modules |
37
+ | 3153098762 | 101 | `shared_utils/theme_db/summary.py:263` | PR-description spec drift — banner missing column-header rows promised in PR body | P2 | A (loosely) | initial+final standards-review phases — A is signature/async-shaped, not "promised behavior vs implementation" shaped |
38
+ | 3153098782 | 101 | `shared_utils/theme_db/writer.py:125` | Naming clarity — `_is_set_column_value` reads like it excludes `None` but does not | P2 | none | bugteam audit rubric addendum — no naming-clarity category in A–J |
39
+ | 3153475246 | 102 | `shared_utils/theme_db/config/constants.py:91` | Collection naming — `THEMES_INSERT_REQUIRED_COLUMN_NAMES` is a tuple and must use the `ALL_*` prefix (CODE_RULES.md §5 "Extended naming rules" → Collections: `all_orders`, `all_users`) | P1 | none | harness PreToolUse hook AND bugteam pre-flight gate — no detector for the collection-prefix rule. Reproduced at `e4abf52c`; renamed to `ALL_THEMES_INSERT_REQUIRED_COLUMN_NAMES` in the merged PR head |
40
+ | 3153475297 | 102 | `shared_utils/theme_db/writer.py:296` | Collection naming — parameter `column_value_pairs` is a list and must use the `all_*` prefix | P1 | none | harness PreToolUse hook AND bugteam pre-flight gate — same gap as 3153475246 for parameter names |
41
+ | 3153475331 | 102 | `shared_utils/theme_db/summary.py:206` (referenced; underlying defect is in `shared_utils/theme_db/tracker.py` `flush()`) | Wrapper plumb-through — public `tracker.flush(*, output_folder)` silently drops `loud_banner_stream` that `ThemeDatabaseWriteSummary.flush(*, output_folder, loud_banner_stream=None)` accepts | P1 | A (loosely) | bugteam audit rubric addendum — A focuses on signatures/return types, not on whether a wrapper preserves the optional kwargs of the function it delegates to |
42
42
 
43
43
  ### Rubric coverage diff
44
44
 
@@ -77,7 +77,7 @@ Source: every detector inside `~/.claude/hooks/blocking/code_rules_enforcer.py`
77
77
  - `check_skip_decorators_in_tests` / `check_existence_check_tests` / `check_constant_equality_tests` (enforcer.py:1079-1277) — test-file checks. Catches none.
78
78
  - `check_unused_optional_parameters` (enforcer.py:1854-1933) — same-file callers must vary an optional parameter. Does not catch the inverse case where a wrapper drops an underlying function's optional kwarg from its own signature. Misses 3153475331.
79
79
  - `check_incomplete_mocks` / `check_duplicated_format_patterns` (enforcer.py:1746-1851) — advisory-only on test files / repeated f-strings. Catches none.
80
- - `advise_file_line_count` (enforcer.py:379-399) — soft advisory at 400, hard advisory at 1000; never blocking. Triggers on PR #70 `writer.py` (446 lines) but is stderr-only, so the bugteam gate exit code stays 0 and the audit/fix loop never sees the signal as a finding. Misses 3153098689 in practice.
80
+ - `advise_file_line_count` (enforcer.py:379-399) — soft advisory at 400, hard advisory at 1000; never blocking. Triggers on PR #101 `writer.py` (446 lines) but is stderr-only, so the bugteam gate exit code stays 0 and the audit/fix loop never sees the signal as a finding. Misses 3153098689 in practice.
81
81
 
82
82
  `bugteam_code_rules_gate.py` adds no detectors of its own — `run_gate` (gate.py:379-443) only filters violations to the changed-line set and routes blocking/advisory output. Extending the gate without extending `validate_content` (or adding sibling detectors invoked from `run_gate`) cannot close the gap.
83
83
 
@@ -97,7 +97,7 @@ The bugteam audit rubric (PROMPTS.md §bug_categories A–J) and the determinist
97
97
 
98
98
  > **Historical — the rubric/phases portion below was reverted in PR #292.** Sections (a) PROMPTS.md K–N addendum, (b) Step 2.6 INITIAL standards review, and the FINAL standards review (Step 3.5) were reverted and replaced by deterministic validators (see status banner at the top of this file). Sections that landed deterministic validators in `code_rules_enforcer.py` (collection prefix, library print, string-literal magic, inline literal collections, loop-variable naming, parameter/return annotations) remain in force. Treat the rubric/phase sections below as record-of-what-was-tried, not a current to-do list.
99
99
 
100
- Each section names exactly one target file, the literal text or regex to add, and a verification step that re-runs the new detection against the PR #70 / PR #73 diffs.
100
+ Each section names exactly one target file, the literal text or regex to add, and a verification step that re-runs the new detection against the PR #101 / PR #102 diffs.
101
101
 
102
102
  ### a. `~/.claude/skills/bugteam/PROMPTS.md`
103
103
 
@@ -143,7 +143,7 @@ Each section names exactly one target file, the literal text or regex to add, an
143
143
 
144
144
  <copilot_derived_addendum_source>
145
145
  The K–N categories were added after Copilot raised real findings on
146
- PR #70 (writer.py / summary.py) and PR #73 (constants.py / writer.py /
146
+ PR #101 (writer.py / summary.py) and PR #102 (constants.py / writer.py /
147
147
  tracker.py) that converged "0 P0 / 0 P1 / 0 P2" under the original
148
148
  A–J rubric. See ~/.claude/skills/bugteam/reference/copilot-gap-analysis.md
149
149
  for the inventory and the validators that now back categories K and L.
@@ -158,7 +158,7 @@ Each section names exactly one target file, the literal text or regex to add, an
158
158
  python $HOME/.claude/skills/bugteam/scripts/bugteam_code_rules_gate.py /tmp/pr70_writer.py /tmp/pr70_summary.py /tmp/pr73_constants.py /tmp/pr73_writer.py
159
159
  ```
160
160
 
161
- Run after the K and L deterministic detectors land in §c/d below; the categories M and N stay rubric-only and are exercised by replaying PR #70 / PR #73 through `/bugteam` with the new PROMPTS.md and observing that the audit posts findings keyed to lines 158 (M), 125 (rubric N — naming clarity), 263 (rubric N — PR-description drift), 361 (initial/final standards review — file length), and 206 (N — wrapper plumb-through).
161
+ Run after the K and L deterministic detectors land in §c/d below; the categories M and N stay rubric-only and are exercised by replaying PR #101 / PR #102 through `/bugteam` with the new PROMPTS.md and observing that the audit posts findings keyed to lines 158 (M), 125 (rubric N — naming clarity), 263 (rubric N — PR-description drift), 361 (initial/final standards review — file length), and 206 (N — wrapper plumb-through).
162
162
 
163
163
  ### b. `~/.claude/skills/bugteam/SKILL.md`
164
164
 
@@ -228,7 +228,7 @@ Loop log:
228
228
  **Verification step:**
229
229
 
230
230
  ```
231
- gh pr diff 70 -R JonEcho/python-automation > /tmp/pr70.diff && gh pr diff 73 -R JonEcho/python-automation > /tmp/pr73.diff && /bugteam --dry-run --replay 70 73
231
+ gh pr diff 101 -R example-org/example-repo > /tmp/pr101.diff && gh pr diff 102 -R example-org/example-repo > /tmp/pr102.diff && /bugteam --dry-run --replay 101 102
232
232
  ```
233
233
 
234
234
  Followed by reading `<team_temp_dir>/pr-70/initial-review.outcomes.xml` and `final-review.outcomes.xml` to confirm Copilot finding ids 3153098661, 3153098689, 3153098727, 3153098762, 3153098782, 3153475246, 3153475297, 3153475331 are surfaced as new entries in either the INITIAL or FINAL review (or both) and that `Step 3.5` upgrades the exit reason when any P0/P1 finding remains.
@@ -505,7 +505,7 @@ Expected output after the patch lands: a list containing `Line 91: Collection co
505
505
 
506
506
  ## Cross-references
507
507
 
508
- - Inventory data sources: live `gh api repos/JonEcho/python-automation/pulls/{70,73}/comments` filtered to `Copilot`; verbatim bodies preserved in the inventory table above.
509
- - Original-commit content used to confirm violations: PR #70 head `29117309cf4ec1e83883160d8c819e0843f9c3ac`; PR #73 review-time commit `e4abf52c3a6c724b4e64bfed0d979cd60a2c8bf0`; PR #73 merged head `c9c935a96cc59d39d623dc7eddda3d341007607c`.
508
+ - Inventory data sources: live `gh api repos/example-org/example-repo/pulls/{101,102}/comments` filtered to `Copilot`; verbatim bodies preserved in the inventory table above.
509
+ - Original-commit content used to confirm violations: PR #101 head `29117309cf4ec1e83883160d8c819e0843f9c3ac`; PR #102 review-time commit `e4abf52c3a6c724b4e64bfed0d979cd60a2c8bf0`; PR #102 merged head `c9c935a96cc59d39d623dc7eddda3d341007607c`.
510
510
  - CODE_RULES.md sections invoked by the patch plan: §⚡ Magic values; §5 Extended naming rules (collections `all_orders`, `all_users`); §6.5 File length guidance; §7 Right-Sized Engineering; §10 No redundant data fetches (used as analogue for wrapper plumb-through).
511
511
  - Constraints honored: `gh-body-file` (no `gh ... --body` calls in the new code paths), `no-shell-substitution` (no `$(...)` in the verification commands above; multi-step shell flows are written as separate Bash invocations or `&&`-chained literal strings).
@@ -6,7 +6,7 @@ Audit every category A through P. Each returns Shape A finding or Shape B verifi
6
6
 
7
7
  Hit a wall on this step? Spawn a background agent to record it. Keep working.
8
8
 
9
- 1. Spawn `Agent(run_in_background=true)` — brief it: check `jl-cmd/claude-code-config` for an open PR touching this file, add a commit to it, or create a fresh branch + worktree + draft PR.
9
+ 1. Spawn `Agent(run_in_background=true)` — brief it: check `jl-cmd/claude-dev-env` for an open PR touching this file, add a commit to it, or create a fresh branch + worktree + draft PR.
10
10
  2. Tell the agent your obstacle and what fixed it. It writes the entry below.
11
11
 
12
12
  ## Obstacles
@@ -137,7 +137,7 @@ Shape: dict keyed by `finding_id`. Each value: `{finding_comment_id, finding_com
137
137
 
138
138
  When `/bugteam` runs against multiple PRs across repos, each PR operates as a
139
139
  logical sub-team within the master `bugteam` team. The PR identity token is
140
- `{owner}/{repo}#{N}` (e.g. `jl-cmd/claude-code-config#422`). The slugified form
140
+ `{owner}/{repo}#{N}` (e.g. `jl-cmd/claude-dev-env#422`). The slugified form
141
141
  comes from `slugify_pr_identity(owner, repo, pr_number)` in
142
142
  [`_path_resolver.py`](../../_shared/pr-loop/scripts/_path_resolver.py).
143
143
 
@@ -154,7 +154,9 @@ Self-claiming by task subject prefix keeps each teammate on its assigned PR.
154
154
  run` issue comment via the Step 2.5 issue-comments fallback endpoint after
155
155
  every successful FIX push, to re-trigger Cursor's bugbot on the new commit.
156
156
 
157
- **Opt-out gate.** When `CLAUDE_REVIEWS_DISABLED` (comma-separated,
158
- case-insensitive, whitespace-tolerant) contains the token `bugbot`, the FIX
159
- subagent skips the re-trigger post even when the flag is present. The rest of
160
- the bugteam audit/fix cycle continues unchanged.
157
+ **Availability gate.** Cursor Bugbot is off by default. The FIX subagent
158
+ posts the re-trigger only when bugbot is enabled for the run — when
159
+ `CLAUDE_REVIEWS_ENABLED` (comma-separated, case-insensitive,
160
+ whitespace-tolerant) lists `bugbot` and `CLAUDE_REVIEWS_DISABLED` does not.
161
+ Otherwise it skips the re-trigger post even when the flag is present, and the
162
+ rest of the bugteam audit/fix cycle continues unchanged.
@@ -5,7 +5,7 @@ joined without a space only inside unfinished markdown link targets), then
5
5
  wrap with textwrap. Preserves fenced blocks verbatim.
6
6
 
7
7
  Same algorithm as ``packages/claude-dev-env/skills/pr-converge/scripts/reflow_skill_md.py``
8
- from https://github.com/jl-cmd/claude-code-config/pull/349 (branch
8
+ from https://github.com/jl-cmd/claude-dev-env/pull/349 (branch
9
9
  ``cursor/pr-converge-skill-line-wrap-ecd1``); ``SKILL_PATH`` points at bugteam
10
10
  ``SKILL.md``. Link reference definitions (``[id]: url``) are treated as logical
11
11
  line starts so they are not merged with prior paragraphs.