claude-dev-env 2.0.2 → 2.2.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 (110) hide show
  1. package/CLAUDE.md +1 -1
  2. package/hooks/blocking/CLAUDE.md +1 -0
  3. package/hooks/blocking/code_rules_shared.py +126 -47
  4. package/hooks/blocking/config/CLAUDE.md +2 -0
  5. package/hooks/blocking/config/verified_commit_context_constants.py +21 -0
  6. package/hooks/blocking/config/verified_commit_gate_output_constants.py +14 -0
  7. package/hooks/blocking/conftest.py +36 -30
  8. package/hooks/blocking/convergence_gate_blocker.py +76 -8
  9. package/hooks/blocking/plain_language_blocker.py +8 -0
  10. package/hooks/blocking/state_description_blocker.py +4 -1
  11. package/hooks/blocking/test_code_rules_shared.py +120 -20
  12. package/hooks/blocking/test_convergence_gate_blocker.py +146 -0
  13. package/hooks/blocking/test_plain_language_blocker.py +15 -0
  14. package/hooks/blocking/test_state_description_blocker.py +15 -0
  15. package/hooks/blocking/test_verified_commit_config_bootstrap.py +18 -0
  16. package/hooks/blocking/test_verified_commit_gate_additional_context.py +134 -0
  17. package/hooks/blocking/tests/test_verified_commit_gate.py +41 -0
  18. package/hooks/blocking/verified_commit_config_bootstrap.py +22 -10
  19. package/hooks/blocking/verified_commit_gate.py +113 -568
  20. package/hooks/blocking/verified_commit_gate_parts/CLAUDE.md +28 -0
  21. package/hooks/blocking/verified_commit_gate_parts/__init__.py +1 -0
  22. package/hooks/blocking/verified_commit_gate_parts/command_tokenization.py +174 -0
  23. package/hooks/blocking/verified_commit_gate_parts/deny_payload.py +53 -0
  24. package/hooks/blocking/verified_commit_gate_parts/deny_reason.py +80 -0
  25. package/hooks/blocking/verified_commit_gate_parts/directory_resolution.py +170 -0
  26. package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +205 -0
  27. package/hooks/blocking/verified_commit_gate_parts/tests/conftest.py +10 -0
  28. package/hooks/blocking/verified_commit_gate_parts/tests/test_command_tokenization.py +94 -0
  29. package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_payload.py +17 -0
  30. package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_reason.py +38 -0
  31. package/hooks/blocking/verified_commit_gate_parts/tests/test_directory_resolution.py +71 -0
  32. package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +61 -0
  33. package/hooks/hooks_constants/CLAUDE.md +4 -1
  34. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
  35. package/hooks/hooks_constants/convergence_gate_blocker_constants.py +36 -0
  36. package/hooks/hooks_constants/harness_scratchpad_constants.py +10 -9
  37. package/hooks/hooks_constants/mypy_integration_constants.py +16 -0
  38. package/hooks/validators/mypy_integration.py +145 -24
  39. package/hooks/validators/python_antipattern_checks.py +16 -0
  40. package/hooks/validators/run_all_validators.py +9 -3
  41. package/hooks/validators/test_mypy_integration.py +154 -0
  42. package/hooks/validators/test_python_antipattern_checks.py +112 -1
  43. package/hooks/validators/test_run_all_validators_pretooluse.py +16 -0
  44. package/package.json +1 -1
  45. package/rules/CLAUDE.md +1 -0
  46. package/rules/verified-commit-gate-skip.md +28 -0
  47. package/skills/_shared/pr-loop/CLAUDE.md +18 -13
  48. package/skills/_shared/pr-loop/portable-driver.md +150 -0
  49. package/skills/_shared/pr-loop/scripts/CLAUDE.md +3 -0
  50. package/skills/_shared/pr-loop/scripts/build_converge_task_list.py +310 -0
  51. package/skills/_shared/pr-loop/scripts/portable_converge_driver.py +1637 -0
  52. package/skills/_shared/pr-loop/scripts/select_converge_pacer.py +215 -0
  53. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +3 -0
  54. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/converge_task_list_constants.py +56 -0
  55. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/pacer_constants.py +47 -0
  56. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/portable_driver_constants.py +181 -0
  57. package/skills/_shared/pr-loop/scripts/test_build_converge_task_list.py +140 -0
  58. package/skills/_shared/pr-loop/scripts/test_portable_converge_driver.py +1119 -0
  59. package/skills/_shared/pr-loop/scripts/test_select_converge_pacer.py +207 -0
  60. package/skills/auditing-claude-config/CLAUDE.md +2 -1
  61. package/skills/auditing-claude-config/SKILL.md +114 -176
  62. package/skills/auditing-claude-config/reference/probe-hook.md +74 -0
  63. package/skills/autoconverge/CLAUDE.md +6 -3
  64. package/skills/autoconverge/SKILL.md +421 -346
  65. package/skills/autoconverge/reference/CLAUDE.md +1 -0
  66. package/skills/autoconverge/reference/convergence.md +5 -5
  67. package/skills/autoconverge/reference/copilot-findings.md +51 -0
  68. package/skills/autoconverge/reference/multi-pr.md +33 -2
  69. package/skills/autoconverge/reference/stop-conditions.md +9 -6
  70. package/skills/autoconverge/test_portable_pacer_gate.py +54 -0
  71. package/skills/closeout/SKILL.md +7 -9
  72. package/skills/copilot-finding-triage/SKILL.md +21 -11
  73. package/skills/copilot-review/CLAUDE.md +3 -2
  74. package/skills/copilot-review/SKILL.md +119 -155
  75. package/skills/copilot-review/templates/subagent-prompt.md +49 -0
  76. package/skills/fresh-branch/CLAUDE.md +6 -9
  77. package/skills/fresh-branch/SKILL.md +84 -33
  78. package/skills/fresh-branch/scripts/create_fresh_branch.py +445 -0
  79. package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/__init__.py +1 -0
  80. package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +74 -0
  81. package/skills/fresh-branch/scripts/test_create_fresh_branch.py +670 -0
  82. package/skills/pr-converge/CLAUDE.md +4 -3
  83. package/skills/pr-converge/SKILL.md +469 -422
  84. package/skills/pr-converge/reference/CLAUDE.md +1 -0
  85. package/skills/pr-converge/reference/multi-pr-orchestration.md +5 -1
  86. package/skills/pr-converge/reference/per-tick.md +51 -24
  87. package/skills/pr-converge/reference/progress-checklist.md +168 -0
  88. package/skills/pr-converge/test_portable_pacer_gate.py +67 -0
  89. package/skills/pr-converge/test_step5_host_branch.py +8 -6
  90. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +5 -3
  91. package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
  92. package/skills/privacy-hygiene/SKILL.md +68 -115
  93. package/skills/privacy-hygiene/reference/sweep-procedure.md +62 -0
  94. package/skills/session-log/CLAUDE.md +2 -1
  95. package/skills/session-log/SKILL.md +4 -26
  96. package/skills/session-log/templates/frontmatter.md +40 -0
  97. package/skills/skill-builder/CLAUDE.md +8 -7
  98. package/skills/skill-builder/SKILL.md +26 -11
  99. package/skills/skill-builder/references/CLAUDE.md +3 -1
  100. package/skills/skill-builder/references/delegation-map.md +21 -12
  101. package/skills/skill-builder/references/description-field.md +9 -11
  102. package/skills/skill-builder/references/deterministic-elements.md +218 -0
  103. package/skills/skill-builder/references/self-audit-checklist.md +62 -45
  104. package/skills/skill-builder/references/skill-modularity.md +8 -9
  105. package/skills/skill-builder/templates/CLAUDE.md +2 -2
  106. package/skills/skill-builder/templates/gap-analysis.md +15 -0
  107. package/skills/skill-builder/workflows/CLAUDE.md +5 -5
  108. package/skills/skill-builder/workflows/improve-skill.md +18 -9
  109. package/skills/skill-builder/workflows/new-skill.md +23 -15
  110. package/skills/skill-builder/workflows/polish-skill.md +28 -21
@@ -0,0 +1,207 @@
1
+ """Tests for select_converge_pacer — host tool surface → pacer mapping."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import subprocess
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ import pytest
11
+
12
+ _SCRIPTS_DIR = Path(__file__).resolve().parent
13
+ if str(_SCRIPTS_DIR) not in sys.path:
14
+ sys.path.insert(0, str(_SCRIPTS_DIR))
15
+
16
+ import select_converge_pacer as pacer_module # noqa: E402
17
+ from skills_pr_loop_constants.pacer_constants import ( # noqa: E402
18
+ CLI_HAS_SCHEDULE_WAKEUP_FLAG,
19
+ CLI_HAS_WORKFLOW_FLAG,
20
+ CLI_SKILL_FLAG,
21
+ ENTRY_SKILL_AUTOCONVERGE,
22
+ ENTRY_SKILL_PR_CONVERGE,
23
+ EXIT_SUCCESS,
24
+ EXIT_USAGE_ERROR,
25
+ PACER_PORTABLE,
26
+ PACER_SCHEDULE_WAKEUP,
27
+ PACER_WORKFLOW,
28
+ RESULT_KEY_ENTRY_SKILL,
29
+ RESULT_KEY_HAS_SCHEDULE_WAKEUP,
30
+ RESULT_KEY_HAS_WORKFLOW,
31
+ RESULT_KEY_PACER,
32
+ )
33
+
34
+ SCRIPT_PATH = _SCRIPTS_DIR / "select_converge_pacer.py"
35
+
36
+
37
+ def test_autoconverge_with_workflow_selects_workflow() -> None:
38
+ selection = pacer_module.select_converge_pacer(
39
+ entry_skill=ENTRY_SKILL_AUTOCONVERGE,
40
+ has_workflow=True,
41
+ has_schedule_wakeup=False,
42
+ )
43
+ assert selection.pacer == PACER_WORKFLOW
44
+ assert selection.entry_skill == ENTRY_SKILL_AUTOCONVERGE
45
+
46
+
47
+ def test_autoconverge_without_workflow_selects_portable() -> None:
48
+ selection = pacer_module.select_converge_pacer(
49
+ entry_skill=ENTRY_SKILL_AUTOCONVERGE,
50
+ has_workflow=False,
51
+ has_schedule_wakeup=True,
52
+ )
53
+ assert selection.pacer == PACER_PORTABLE
54
+
55
+
56
+ def test_pr_converge_with_schedule_wakeup_selects_schedule_wakeup() -> None:
57
+ selection = pacer_module.select_converge_pacer(
58
+ entry_skill=ENTRY_SKILL_PR_CONVERGE,
59
+ has_workflow=False,
60
+ has_schedule_wakeup=True,
61
+ )
62
+ assert selection.pacer == PACER_SCHEDULE_WAKEUP
63
+ assert selection.entry_skill == ENTRY_SKILL_PR_CONVERGE
64
+
65
+
66
+ def test_pr_converge_without_schedule_wakeup_selects_portable() -> None:
67
+ selection = pacer_module.select_converge_pacer(
68
+ entry_skill=ENTRY_SKILL_PR_CONVERGE,
69
+ has_workflow=True,
70
+ has_schedule_wakeup=False,
71
+ )
72
+ assert selection.pacer == PACER_PORTABLE
73
+
74
+
75
+ def test_third_party_surface_selects_portable_for_both_entries() -> None:
76
+ for each_skill in (ENTRY_SKILL_AUTOCONVERGE, ENTRY_SKILL_PR_CONVERGE):
77
+ selection = pacer_module.select_converge_pacer(
78
+ entry_skill=each_skill,
79
+ has_workflow=False,
80
+ has_schedule_wakeup=False,
81
+ )
82
+ assert selection.pacer == PACER_PORTABLE, each_skill
83
+
84
+
85
+ def test_unknown_entry_skill_raises() -> None:
86
+ with pytest.raises(ValueError, match="entry skill must be one of"):
87
+ pacer_module.select_converge_pacer(
88
+ entry_skill="bugteam",
89
+ has_workflow=False,
90
+ has_schedule_wakeup=False,
91
+ )
92
+
93
+
94
+ def test_parse_bool_flag_accepts_common_tokens() -> None:
95
+ assert pacer_module.parse_bool_flag("1") is True
96
+ assert pacer_module.parse_bool_flag("true") is True
97
+ assert pacer_module.parse_bool_flag("YES") is True
98
+ assert pacer_module.parse_bool_flag("0") is False
99
+ assert pacer_module.parse_bool_flag("false") is False
100
+ assert pacer_module.parse_bool_flag("OFF") is False
101
+
102
+
103
+ def test_parse_bool_flag_rejects_unknown_token() -> None:
104
+ with pytest.raises(ValueError, match="boolean token"):
105
+ pacer_module.parse_bool_flag("maybe")
106
+
107
+
108
+ def test_selection_as_json_dict_uses_stable_keys() -> None:
109
+ selection = pacer_module.select_converge_pacer(
110
+ entry_skill=ENTRY_SKILL_PR_CONVERGE,
111
+ has_workflow=False,
112
+ has_schedule_wakeup=False,
113
+ )
114
+ payload = pacer_module.selection_as_json_dict(selection)
115
+ assert payload[RESULT_KEY_PACER] == PACER_PORTABLE
116
+ assert payload[RESULT_KEY_ENTRY_SKILL] == ENTRY_SKILL_PR_CONVERGE
117
+ assert payload[RESULT_KEY_HAS_WORKFLOW] is False
118
+ assert payload[RESULT_KEY_HAS_SCHEDULE_WAKEUP] is False
119
+
120
+
121
+ def test_cli_prints_json_and_exits_zero() -> None:
122
+ completed = subprocess.run(
123
+ [
124
+ sys.executable,
125
+ str(SCRIPT_PATH),
126
+ "--skill",
127
+ ENTRY_SKILL_AUTOCONVERGE,
128
+ "--has-workflow",
129
+ "0",
130
+ "--has-schedule-wakeup",
131
+ "0",
132
+ ],
133
+ check=False,
134
+ capture_output=True,
135
+ text=True,
136
+ )
137
+ assert completed.returncode == EXIT_SUCCESS, completed.stderr
138
+ payload = json.loads(completed.stdout)
139
+ assert payload[RESULT_KEY_PACER] == PACER_PORTABLE
140
+ assert payload[RESULT_KEY_ENTRY_SKILL] == ENTRY_SKILL_AUTOCONVERGE
141
+
142
+
143
+ def test_cli_rejects_bad_bool_with_usage_exit() -> None:
144
+ completed = subprocess.run(
145
+ [
146
+ sys.executable,
147
+ str(SCRIPT_PATH),
148
+ "--skill",
149
+ ENTRY_SKILL_PR_CONVERGE,
150
+ "--has-workflow",
151
+ "nope",
152
+ "--has-schedule-wakeup",
153
+ "0",
154
+ ],
155
+ check=False,
156
+ capture_output=True,
157
+ text=True,
158
+ )
159
+ assert completed.returncode == EXIT_USAGE_ERROR
160
+ assert "boolean token" in completed.stderr
161
+
162
+
163
+ def test_main_returns_usage_error_on_value_error(
164
+ monkeypatch: pytest.MonkeyPatch,
165
+ ) -> None:
166
+ def raise_value_error(**_kwargs: object) -> object:
167
+ raise ValueError("entry skill must be one of")
168
+
169
+ monkeypatch.setattr(
170
+ pacer_module, "select_converge_pacer", raise_value_error
171
+ )
172
+ exit_code = pacer_module.main(
173
+ [
174
+ "--skill",
175
+ ENTRY_SKILL_PR_CONVERGE,
176
+ "--has-workflow",
177
+ "0",
178
+ "--has-schedule-wakeup",
179
+ "0",
180
+ ]
181
+ )
182
+ assert exit_code == EXIT_USAGE_ERROR
183
+
184
+
185
+ def test_build_argument_parser_requires_skill_and_host_flags() -> None:
186
+ parser = pacer_module.build_argument_parser()
187
+ all_option_strings = {
188
+ each_option
189
+ for each_action in parser._actions
190
+ for each_option in each_action.option_strings
191
+ }
192
+ assert CLI_SKILL_FLAG in all_option_strings
193
+ assert CLI_HAS_WORKFLOW_FLAG in all_option_strings
194
+ assert CLI_HAS_SCHEDULE_WAKEUP_FLAG in all_option_strings
195
+ parsed_namespace = parser.parse_args(
196
+ [
197
+ CLI_SKILL_FLAG,
198
+ ENTRY_SKILL_AUTOCONVERGE,
199
+ CLI_HAS_WORKFLOW_FLAG,
200
+ "1",
201
+ CLI_HAS_SCHEDULE_WAKEUP_FLAG,
202
+ "0",
203
+ ]
204
+ )
205
+ assert parsed_namespace.skill == ENTRY_SKILL_AUTOCONVERGE
206
+ assert parsed_namespace.has_workflow == "1"
207
+ assert parsed_namespace.has_schedule_wakeup == "0"
@@ -9,6 +9,7 @@ Audits a Claude Code setup — user `CLAUDE.md`, `~/.claude/rules/`, project `.c
9
9
  | File | Role |
10
10
  |---|---|
11
11
  | `SKILL.md` | Full audit protocol: inventory the always-loaded set, find duplicate imports, classify each rule, produce the migration table, stage recommendations by risk, and verify lazy-load behavior with an optional probe hook. |
12
+ | `reference/probe-hook.md` | The optional lazy-load probe hook: hook script, `settings.json` registration, and test protocol. |
12
13
 
13
14
  ## What the skill produces
14
15
 
@@ -17,4 +18,4 @@ Audits a Claude Code setup — user `CLAUDE.md`, `~/.claude/rules/`, project `.c
17
18
  3. **Recommended next step** — the single highest-leverage change.
18
19
  4. **Open questions** — anything not verified by the probe hook.
19
20
 
20
- No scripts or workflow files. The skill body holds all logic.
21
+ No runnable scripts or workflow files. The audit logic lives in the skill body; the optional lazy-load probe hook lives in `reference/probe-hook.md` as copy-ready text.
@@ -1,176 +1,114 @@
1
- ---
2
- name: auditing-claude-config
3
- description: >-
4
- Audits a Claude Code setup (user CLAUDE.md, ~/.claude/rules/, project .claude/) for
5
- context-budget waste — duplicate @-imports, always-on rules to path-scope or convert
6
- to skills, oversized files — and produces a migration table with savings. Use when
7
- reviewing the startup/instruction load, when /memory shows surprising loads, when
8
- adding new rules, or for periodic config hygiene.
9
- ---
10
-
11
- # Auditing Claude Config
12
-
13
- This skill audits what gets eagerly loaded into every Claude Code session and identifies wins — duplicate imports, lazy-load candidates, skill-conversion candidates, and pointer-shrink opportunities. It is grounded in three Anthropic docs cited at the bottom.
14
-
15
- ## When to invoke
16
-
17
- - `/memory` shows files the user did not expect to be loaded
18
- - The user is adding new rules and wants to know if the preload is growing past the recommended ceiling (CLAUDE.md target: under 200 lines)
19
- - Sessions feel sluggish or adherence to rules has degraded (the docs warn that bloated CLAUDE.md files cause Claude to ignore actual instructions)
20
- - A new template or shared `.claude/` directory has just been adopted
21
- - Periodic hygiene — quarterly is a reasonable cadence
22
-
23
- ## Background facts the audit relies on
24
-
25
- These come from the official Claude Code documentation; do not re-derive them.
26
-
27
- | Fact | Source phrasing |
28
- |---|---|
29
- | `@path` imports in CLAUDE.md and rules expand into context **at launch** — they are not pointers | "Imported files are expanded and loaded into context at launch alongside the CLAUDE.md that references them" |
30
- | Splitting into `@`-imports does **not** reduce context | "Splitting into `@path` imports helps organization but does not reduce context, since imported files load at launch" |
31
- | Files in `.claude/rules/` without `paths:` frontmatter load **every** session | "Rules without `paths` frontmatter are loaded unconditionally and apply to all files" |
32
- | Path-scoped rules load **lazily** when matching files are accessed | "Rules can be scoped to specific files using YAML frontmatter with the `paths` field. These conditional rules only apply when Claude is working with files matching the specified patterns" |
33
- | Skills preload **metadata only** | "At startup, only the metadata (name and description) from all Skills is pre-loaded. Claude reads SKILL.md only when the Skill becomes relevant, and reads additional files only as needed" |
34
- | `@`-imports inside fenced/inline code blocks do not trigger imports | Empirical (`InstructionsLoaded` hook): a bare `@`-import fires an `include` load event; the same path wrapped in backticks fires none, so the referenced file stays out of session-start context |
35
-
36
- ## Audit workflow
37
-
38
- ### Step 1 — Inventory the always-loaded set
39
-
40
- ```
41
- Files to count:
42
- ~/.claude/CLAUDE.md
43
- ~/.claude/CLAUDE.local.md (if present)
44
- ./CLAUDE.md (project root)
45
- ./.claude/CLAUDE.md (project alt)
46
- ./CLAUDE.local.md
47
- every file in ~/.claude/rules/ without `paths:` frontmatter
48
- every file in ./.claude/rules/ without `paths:` frontmatter
49
- every file referenced via @-import from any of the above (recursively, max depth 5)
50
- ```
51
-
52
- Count lines with `wc -l` (cygwin/Git Bash) or `Get-Content … | Measure-Object -Line` (PowerShell). Sum is the always-loaded line budget.
53
-
54
- Flag the result against the docs:
55
- - CLAUDE.md alone over 200 lines → strong nudge to slim
56
- - Total preload over ~1,000 lines → likely losing instruction adherence
57
-
58
- ### Step 2 — Find duplicate `@`-imports
59
-
60
- Search every always-loaded file (CLAUDE.md and every rule file without `paths:`) for `@`-references. Build a multimap of `imported_path → [referrer_path, ...]`. Any entry with two or more referrers is loading the import twice into context.
61
-
62
- Fix: delete the import from one of the parents (keep it in the file with the broader scope, typically CLAUDE.md).
63
-
64
- ### Step 3 — Classify each rule
65
-
66
- For every rule in `~/.claude/rules/` (and project `.claude/rules/`), apply this matrix:
67
-
68
- | Rule body describes | Verdict | Action |
69
- |---|---|---|
70
- | Behavior that applies every turn (TDD, conservative-action, ask-via-tool, etc.) | Keep always-on | No change |
71
- | File-type-specific patterns (Python idioms, JS/TS, Windows fs, test patterns) | Path-scope | Add `paths:` frontmatter with appropriate globs |
72
- | A multi-step workflow or procedure | Convert to skill | Move body to `~/.claude/skills/<name>/SKILL.md`, leave a 2-3 line pointer rule |
73
- | Content already covered by an existing skill in `~/.claude/skills/` | Shrink to pointer | Replace body with a 2-3 line reference to the skill |
74
- | Reference doc consumed only by one rule | Inline or co-locate | Move into the consumer rule or skill, drop the standalone doc |
75
-
76
- When suggesting `paths:` globs, derive them from the rule's body — do not guess. Examples:
77
- - Body discusses `shutil.rmtree`, `os.unlink` → `paths: ["**/*.py"]`
78
- - Body discusses `mkdirSync`, `fs.promises` → `paths: ["**/*.{mjs,js,ts}"]`
79
- - Body discusses pytest fixtures, test naming → `paths: ["**/test_*.py", "**/*_test.py", "**/conftest.py"]`
80
-
81
- ### Step 4 — Produce the migration table
82
-
83
- Output one table with these columns: `Rule | Lines today | Verdict | Specific action | Lines removed from preload`. Total the savings. Express as both an absolute line count and a percentage of step 1's baseline.
84
-
85
- ### Step 5 — Stage the changes
86
-
87
- Group recommendations by risk:
88
- - **Zero-risk:** duplicate-import deletion (largest single win in most setups)
89
- - **Low-risk:** adding `paths:` frontmatter (the docs guarantee fallback to "applies to all files" if syntax is wrong; verify with `/memory`)
90
- - **Medium-risk:** moving content into skills (changes when content reaches Claude — skill-discovery dependent)
91
- - **Author-required:** shrinking rules to pointers (requires deciding what content survives)
92
-
93
- ## Empirical verification (optional)
94
-
95
- If the audit's recommendations rest on assumptions about lazy-load behavior — especially `@`-imports nested inside path-scoped rules — install this probe hook to capture every `InstructionsLoaded` event.
96
-
97
- ### Hook script
98
-
99
- Path: `~/.claude/hooks/observability/instructions_loaded_logger.py`
100
-
101
- ```python
102
- #!/usr/bin/env python3
103
- import json
104
- import sys
105
- from datetime import datetime, timezone
106
- from pathlib import Path
107
-
108
-
109
- def main() -> int:
110
- log_path = Path.home() / ".claude" / "logs" / "instructions_loaded.jsonl"
111
- try:
112
- payload = json.load(sys.stdin)
113
- record = {
114
- "timestamp": datetime.now(timezone.utc).isoformat(),
115
- "file_path": payload.get("file_path"),
116
- "load_reason": payload.get("load_reason"),
117
- "memory_type": payload.get("memory_type"),
118
- "trigger_file_path": payload.get("trigger_file_path"),
119
- "parent_file_path": payload.get("parent_file_path"),
120
- "globs": payload.get("globs"),
121
- "session_id": payload.get("session_id"),
122
- }
123
- except Exception as exception:
124
- record = {
125
- "timestamp": datetime.now(timezone.utc).isoformat(),
126
- "error": str(exception),
127
- }
128
- try:
129
- log_path.parent.mkdir(parents=True, exist_ok=True)
130
- with log_path.open("a", encoding="utf-8") as log_file:
131
- log_file.write(json.dumps(record) + "\n")
132
- except OSError:
133
- pass
134
- return 0
135
-
136
-
137
- if __name__ == "__main__":
138
- raise SystemExit(main())
139
- ```
140
-
141
- ### settings.json registration
142
-
143
- Merge under `hooks.InstructionsLoaded` in `~/.claude/settings.json`:
144
-
145
- ```json
146
- {
147
- "matcher": "session_start|nested_traversal|path_glob_match|include|compact",
148
- "hooks": [
149
- {
150
- "type": "command",
151
- "command": "python ~/.claude/hooks/observability/instructions_loaded_logger.py"
152
- }
153
- ]
154
- }
155
- ```
156
-
157
- ### Test protocol
158
-
159
- 1. Start a fresh Claude Code session in a directory with no test files (`*.test.*`, `test_*.py`, etc.).
160
- 2. Read `~/.claude/logs/instructions_loaded.jsonl`. Every entry should have `load_reason: "session_start"` (plus any `nested_traversal` for ancestor CLAUDE.md files). Entries for path-scoped rules should be absent.
161
- 3. Open a `.test.tsx` (or `test_foo.py`) file. Re-read the log. New entries should have `load_reason: "path_glob_match"` for the rule itself, and `load_reason: "include"` (with `parent_file_path` pointing at the rule) for any `@`-imports nested inside it.
162
- 4. Acceptance: a rule classified as path-scoped does not appear in step 2 but does appear in step 3. Its nested imports follow the same pattern.
163
-
164
- ## Output format
165
-
166
- Always end an audit run with:
167
- 1. **Baseline:** total always-loaded lines today, broken down by file
168
- 2. **Findings:** the migration table from step 4
169
- 3. **Recommended next step:** the single highest-leverage change (usually duplicate-import deletion)
170
- 4. **Open questions:** anything not verified empirically
171
-
172
- ## Sources
173
-
174
- - [Claude Code — How Claude remembers your project](https://code.claude.com/docs/en/memory)
175
- - [Claude Code — Hooks (InstructionsLoaded)](https://code.claude.com/docs/en/hooks)
176
- - [Claude API — Skill authoring best practices](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices)
1
+ ---
2
+ name: auditing-claude-config
3
+ description: >-
4
+ Audits a Claude Code setup (user CLAUDE.md, ~/.claude/rules/, project .claude/) for
5
+ context-budget waste — duplicate @-imports, always-on rules to path-scope or convert
6
+ to skills, oversized files — and produces a migration table with savings. Use when
7
+ reviewing the startup/instruction load, when /memory shows surprising loads, when
8
+ adding new rules, or for periodic config hygiene.
9
+ ---
10
+
11
+ # Auditing Claude Config
12
+
13
+ This skill audits what gets eagerly loaded into every Claude Code session and identifies wins — duplicate imports, lazy-load candidates, skill-conversion candidates, and pointer-shrink opportunities. It is grounded in three Anthropic docs cited at the bottom.
14
+
15
+ ## When to invoke
16
+
17
+ - `/memory` shows files the user did not expect to be loaded
18
+ - The user is adding new rules and wants to know if the preload is growing past the recommended ceiling (CLAUDE.md target: under 200 lines)
19
+ - Sessions feel sluggish or adherence to rules has degraded (the docs warn that bloated CLAUDE.md files cause Claude to ignore actual instructions)
20
+ - A new template or shared `.claude/` directory has just been adopted
21
+ - Periodic hygiene — quarterly is a reasonable cadence
22
+
23
+ ## Background facts the audit relies on
24
+
25
+ These come from the official Claude Code documentation; do not re-derive them.
26
+
27
+ | Fact | Source phrasing |
28
+ |---|---|
29
+ | `@path` imports in CLAUDE.md and rules expand into context **at launch** — they are not pointers | "Imported files are expanded and loaded into context at launch alongside the CLAUDE.md that references them" |
30
+ | Splitting into `@`-imports does **not** reduce context | "Splitting into `@path` imports helps organization but does not reduce context, since imported files load at launch" |
31
+ | Files in `.claude/rules/` without `paths:` frontmatter load **every** session | "Rules without `paths` frontmatter are loaded unconditionally and apply to all files" |
32
+ | Path-scoped rules load **lazily** when matching files are accessed | "Rules can be scoped to specific files using YAML frontmatter with the `paths` field. These conditional rules only apply when Claude is working with files matching the specified patterns" |
33
+ | Skills preload **metadata only** | "At startup, only the metadata (name and description) from all Skills is pre-loaded. Claude reads SKILL.md only when the Skill becomes relevant, and reads additional files only as needed" |
34
+ | `@`-imports inside fenced/inline code blocks do not trigger imports | Empirical (verified in this skill's source session) referenced files alongside backtick-wrapped `@` paths do not appear in session-start context |
35
+
36
+ ## Audit workflow
37
+
38
+ ### Step 1 — Inventory the always-loaded set
39
+
40
+ ```
41
+ Files to count:
42
+ ~/.claude/CLAUDE.md
43
+ ~/.claude/CLAUDE.local.md (if present)
44
+ ./CLAUDE.md (project root)
45
+ ./.claude/CLAUDE.md (project alt)
46
+ ./CLAUDE.local.md
47
+ every file in ~/.claude/rules/ without `paths:` frontmatter
48
+ every file in ./.claude/rules/ without `paths:` frontmatter
49
+ every file referenced via @-import from any of the above (recursively, max depth 5)
50
+ ```
51
+
52
+ Count lines with `wc -l` (cygwin/Git Bash) or `Get-Content … | Measure-Object -Line` (PowerShell). Sum is the always-loaded line budget.
53
+
54
+ Flag the result against the docs:
55
+ - CLAUDE.md alone over 200 lines → strong nudge to slim
56
+ - Total preload over ~1,000 lines → likely losing instruction adherence
57
+
58
+ ### Step 2 — Find duplicate `@`-imports
59
+
60
+ Search every always-loaded file (CLAUDE.md and every rule file without `paths:`) for `@`-references. Build a multimap of `imported_path → [referrer_path, ...]`. Any entry with two or more referrers is loading the import twice into context.
61
+
62
+ Fix: delete the import from one of the parents (keep it in the file with the broader scope, typically CLAUDE.md).
63
+
64
+ ### Step 3 — Classify each rule
65
+
66
+ For every rule in `~/.claude/rules/` (and project `.claude/rules/`), apply this matrix:
67
+
68
+ | Rule body describes | Verdict | Action |
69
+ |---|---|---|
70
+ | Behavior that applies every turn (TDD, conservative-action, ask-via-tool, etc.) | Keep always-on | No change |
71
+ | File-type-specific patterns (Python idioms, JS/TS, Windows fs, test patterns) | Path-scope | Add `paths:` frontmatter with appropriate globs |
72
+ | A multi-step workflow or procedure | Convert to skill | Move body to `~/.claude/skills/<name>/SKILL.md`, leave a 2-3 line pointer rule |
73
+ | Content already covered by an existing skill in `~/.claude/skills/` | Shrink to pointer | Replace body with a 2-3 line reference to the skill |
74
+ | Reference doc consumed only by one rule | Inline or co-locate | Move into the consumer rule or skill, drop the standalone doc |
75
+
76
+ When suggesting `paths:` globs, derive them from the rule's body — do not guess. Examples:
77
+ - Body discusses `shutil.rmtree`, `os.unlink` → `paths: ["**/*.py"]`
78
+ - Body discusses `mkdirSync`, `fs.promises` → `paths: ["**/*.{mjs,js,ts}"]`
79
+ - Body discusses pytest fixtures, test naming → `paths: ["**/test_*.py", "**/*_test.py", "**/conftest.py"]`
80
+
81
+ ### Step 4 — Produce the migration table
82
+
83
+ Output one table with these columns: `Rule | Lines today | Verdict | Specific action | Lines removed from preload`. Total the savings. Express as both an absolute line count and a percentage of step 1's baseline.
84
+
85
+ ### Step 5 — Stage the changes
86
+
87
+ Group recommendations by risk:
88
+ - **Zero-risk:** duplicate-import deletion (largest single win in most setups)
89
+ - **Low-risk:** adding `paths:` frontmatter (the docs guarantee fallback to "applies to all files" if syntax is wrong; verify with `/memory`)
90
+ - **Medium-risk:** moving content into skills (changes when content reaches Claude — skill-discovery dependent)
91
+ - **Author-required:** shrinking rules to pointers (requires deciding what content survives)
92
+
93
+ ## Empirical verification (optional)
94
+
95
+ When the audit's recommendations rest on assumptions about lazy-load behavior —
96
+ especially `@`-imports nested inside path-scoped rules — install the probe hook
97
+ in [`reference/probe-hook.md`](reference/probe-hook.md) to capture every
98
+ `InstructionsLoaded` event. It carries the hook script, the `settings.json`
99
+ registration, and the test protocol that confirms a path-scoped rule loads
100
+ lazily while its nested imports follow.
101
+
102
+ ## Output format
103
+
104
+ Always end an audit run with:
105
+ 1. **Baseline:** total always-loaded lines today, broken down by file
106
+ 2. **Findings:** the migration table from step 4
107
+ 3. **Recommended next step:** the single highest-leverage change (usually duplicate-import deletion)
108
+ 4. **Open questions:** anything not verified empirically
109
+
110
+ ## Sources
111
+
112
+ - [Claude Code — How Claude remembers your project](https://code.claude.com/docs/en/memory)
113
+ - [Claude Code — Hooks (InstructionsLoaded)](https://code.claude.com/docs/en/hooks)
114
+ - [Claude API — Skill authoring best practices](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices)
@@ -0,0 +1,74 @@
1
+ # Lazy-load probe hook
2
+
3
+ Optional empirical check for the `auditing-claude-config` skill. When the audit's
4
+ recommendations rest on assumptions about lazy-load behavior — especially
5
+ `@`-imports nested inside path-scoped rules — install this probe hook to capture
6
+ every `InstructionsLoaded` event. The hub ([`../SKILL.md`](../SKILL.md) §
7
+ Empirical verification) points here.
8
+
9
+ ## Hook script
10
+
11
+ Path: `~/.claude/hooks/observability/instructions_loaded_logger.py`
12
+
13
+ ```python
14
+ #!/usr/bin/env python3
15
+ import json
16
+ import sys
17
+ from datetime import datetime, timezone
18
+ from pathlib import Path
19
+
20
+
21
+ def main() -> int:
22
+ log_path = Path.home() / ".claude" / "logs" / "instructions_loaded.jsonl"
23
+ try:
24
+ payload = json.load(sys.stdin)
25
+ record = {
26
+ "timestamp": datetime.now(timezone.utc).isoformat(),
27
+ "file_path": payload.get("file_path"),
28
+ "load_reason": payload.get("load_reason"),
29
+ "memory_type": payload.get("memory_type"),
30
+ "trigger_file_path": payload.get("trigger_file_path"),
31
+ "parent_file_path": payload.get("parent_file_path"),
32
+ "globs": payload.get("globs"),
33
+ "session_id": payload.get("session_id"),
34
+ }
35
+ except Exception as exception:
36
+ record = {
37
+ "timestamp": datetime.now(timezone.utc).isoformat(),
38
+ "error": str(exception),
39
+ }
40
+ try:
41
+ log_path.parent.mkdir(parents=True, exist_ok=True)
42
+ with log_path.open("a", encoding="utf-8") as log_file:
43
+ log_file.write(json.dumps(record) + "\n")
44
+ except OSError:
45
+ pass
46
+ return 0
47
+
48
+
49
+ if __name__ == "__main__":
50
+ raise SystemExit(main())
51
+ ```
52
+
53
+ ## settings.json registration
54
+
55
+ Merge under `hooks.InstructionsLoaded` in `~/.claude/settings.json`:
56
+
57
+ ```json
58
+ {
59
+ "matcher": "session_start|nested_traversal|path_glob_match|include|compact",
60
+ "hooks": [
61
+ {
62
+ "type": "command",
63
+ "command": "python ~/.claude/hooks/observability/instructions_loaded_logger.py"
64
+ }
65
+ ]
66
+ }
67
+ ```
68
+
69
+ ## Test protocol
70
+
71
+ 1. Start a fresh Claude Code session in a directory with no test files (`*.test.*`, `test_*.py`, etc.).
72
+ 2. Read `~/.claude/logs/instructions_loaded.jsonl`. Every entry should have `load_reason: "session_start"` (plus any `nested_traversal` for ancestor CLAUDE.md files). Entries for path-scoped rules should be absent.
73
+ 3. Open a `.test.tsx` (or `test_foo.py`) file. Re-read the log. New entries should have `load_reason: "path_glob_match"` for the rule itself, and `load_reason: "include"` (with `parent_file_path` pointing at the rule) for any `@`-imports nested inside it.
74
+ 4. Acceptance: a rule classified as path-scoped does not appear in step 2 but does appear in step 3. Its nested imports follow the same pattern.
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Trigger:** `/autoconverge`, "autoconverge this PR", "converge this PR in one run", "run the converge workflow", "drive the PR to ready autonomously".
4
4
 
5
- Drives one draft PR to convergence in a single autonomous workflow run. Each round runs a deterministic static sweep first, then a code-review pass, a bug-audit (with its adversarial second pass), and a self-review parity pass in parallel on the same HEAD, deduplicates findings, applies every fix in one commit, and re-verifies. Once the internal lenses are clean, Cursor Bugbot, Copilot, and Codex run as terminal confirmation gates, and the run marks the PR ready on convergence. State lives in the workflow journal; no `ScheduleWakeup` ticks.
5
+ Drives one draft PR to convergence in one autonomous run. On `pacer=workflow`, each round runs a deterministic static sweep first, then a code-review pass, a bug-audit (with its adversarial second pass), and a self-review parity pass in parallel on the same HEAD, deduplicates findings, applies every fix in one commit, and re-verifies; state lives in the workflow journal. On `pacer=portable`, the continuous driver in `_shared/pr-loop/portable-driver.md` runs the shared pr-converge phase machine with the same helpers and `check_convergence.py` ready definition. Once the internal passes are clean, Cursor Bugbot, Copilot, and Codex run as terminal confirmation gates, and the run marks the PR ready on convergence.
6
6
 
7
7
  ## Subdirectories
8
8
 
@@ -15,12 +15,13 @@ Drives one draft PR to convergence in a single autonomous workflow run. Each rou
15
15
 
16
16
  | File | Role |
17
17
  |---|---|
18
- | `SKILL.md` | Full entry-point protocol: pre-flight steps, worktree setup, `Workflow` call, budget-aware round boundaries, teardown, and the per-round convergence loop summary. |
18
+ | `SKILL.md` | Full entry-point protocol: pacer selection, pre-flight steps, worktree setup, Workflow or portable path, budget-aware boundaries, teardown, and the convergence loop summary. |
19
19
  | `workflow/converge.mjs` | Main convergence workflow. Runs a static sweep then the internal lenses each round, applies fixes, runs Bugbot, Copilot, and Codex as terminal gates, checks convergence, and marks the PR ready. |
20
20
  | `workflow/aggregate_runs.py` | Merges every autoconverge journal for a PR into one deduped journal. |
21
21
  | `workflow/convergence_summary.py` | Builds the convergence-summary agent prompt over the merged findings. |
22
22
  | `workflow/render_report.py` | Builds the closing HTML report from the merged journal and summary. |
23
23
  | `reference/convergence.md` | Round shape: the static sweep, the three parallel internal lenses, deduplication, fix commit, the terminal Bugbot, Copilot, and Codex gates, and the ready definition. |
24
+ | `reference/copilot-findings.md` | The Copilot gate tiering, per-finding verification, and the `userReview` return contract. |
24
25
  | `reference/stop-conditions.md` | Every way the run ends short of ready, including the budget stop. |
25
26
  | `reference/gotchas.md` | Hard-won failure lessons. |
26
27
  | `reference/closing-report.md` | The closing HTML report: data source, build steps, publishing. |
@@ -30,4 +31,6 @@ Drives one draft PR to convergence in a single autonomous workflow run. Each rou
30
31
 
31
32
  ## Entry point
32
33
 
33
- Requires the `Workflow` tool. The `SKILL.md` body specifies the exact pre-flight sequence and `Workflow` call.
34
+ Selects `workflow` or `portable` via `select_converge_pacer.py`. Missing
35
+ `Workflow` selects portable and continues — it does not abort. The `SKILL.md`
36
+ body specifies the pre-flight sequence and each pacer's run path.