claude-dev-env 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +1 -1
- package/agents/clean-coder.md +31 -1
- package/audit-rubrics/prompts/category-e-dead-code.md +2 -2
- package/bin/install.test.mjs +2 -2
- package/docs/CLAUDE.md +6 -3
- package/docs/CODE_RULES.md +5 -1
- package/docs/agent-spawn-protocol.md +39 -0
- package/docs/nas-ssh-invocation.md +23 -0
- package/docs/worker-completion-gate.md +33 -0
- package/hooks/blocking/CLAUDE.md +1 -0
- package/hooks/blocking/code_rules_dead_module_constant.py +11 -5
- package/hooks/blocking/code_rules_shared.py +126 -47
- package/hooks/blocking/config/CLAUDE.md +2 -0
- package/hooks/blocking/config/verified_commit_constants.py +8 -1
- package/hooks/blocking/config/verified_commit_context_constants.py +21 -0
- package/hooks/blocking/config/verified_commit_gate_output_constants.py +14 -0
- package/hooks/blocking/conftest.py +36 -30
- package/hooks/blocking/convergence_gate_blocker.py +76 -8
- package/hooks/blocking/plain_language_blocker.py +8 -0
- package/hooks/blocking/state_description_blocker.py +4 -1
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +58 -0
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant_alias.py +133 -0
- package/hooks/blocking/test_code_rules_shared.py +120 -20
- package/hooks/blocking/test_convergence_gate_blocker.py +146 -0
- package/hooks/blocking/test_plain_language_blocker.py +15 -0
- package/hooks/blocking/test_state_description_blocker.py +15 -0
- package/hooks/blocking/test_verification_verdict_store.py +12 -0
- package/hooks/blocking/test_verified_commit_config_bootstrap.py +18 -0
- package/hooks/blocking/test_verified_commit_docs_delta.py +176 -0
- package/hooks/blocking/test_verified_commit_gate_additional_context.py +134 -0
- package/hooks/blocking/tests/test_pii_prevention_blocker.py +0 -1
- package/hooks/blocking/tests/test_verified_commit_gate.py +41 -0
- package/hooks/blocking/verified_commit_config_bootstrap.py +22 -10
- package/hooks/blocking/verified_commit_gate.py +113 -568
- package/hooks/blocking/verified_commit_gate_parts/CLAUDE.md +28 -0
- package/hooks/blocking/verified_commit_gate_parts/__init__.py +1 -0
- package/hooks/blocking/verified_commit_gate_parts/command_tokenization.py +174 -0
- package/hooks/blocking/verified_commit_gate_parts/deny_payload.py +53 -0
- package/hooks/blocking/verified_commit_gate_parts/deny_reason.py +80 -0
- package/hooks/blocking/verified_commit_gate_parts/directory_resolution.py +170 -0
- package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +205 -0
- package/hooks/blocking/verified_commit_gate_parts/tests/conftest.py +10 -0
- package/hooks/blocking/verified_commit_gate_parts/tests/test_command_tokenization.py +94 -0
- package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_payload.py +17 -0
- package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_reason.py +38 -0
- package/hooks/blocking/verified_commit_gate_parts/tests/test_directory_resolution.py +71 -0
- package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +61 -0
- package/hooks/hooks_constants/CLAUDE.md +4 -1
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
- package/hooks/hooks_constants/convergence_gate_blocker_constants.py +36 -0
- package/hooks/hooks_constants/dead_module_constant_constants.py +8 -0
- package/hooks/hooks_constants/harness_scratchpad_constants.py +10 -9
- package/hooks/hooks_constants/mypy_integration_constants.py +16 -0
- package/hooks/hooks_constants/pii_prevention_constants.py +1 -0
- package/hooks/validators/mypy_integration.py +145 -24
- package/hooks/validators/python_antipattern_checks.py +16 -0
- package/hooks/validators/run_all_validators.py +9 -3
- package/hooks/validators/test_mypy_integration.py +154 -0
- package/hooks/validators/test_python_antipattern_checks.py +112 -1
- package/hooks/validators/test_run_all_validators_pretooluse.py +16 -0
- package/package.json +1 -1
- package/rules/CLAUDE.md +3 -3
- package/rules/agent-spawn-protocol.md +5 -43
- package/rules/code-standards.md +1 -36
- package/rules/env-var-table-code-drift.md +2 -21
- package/rules/hook-prose-matches-detector.md +5 -16
- package/rules/nas-ssh-invocation.md +3 -15
- package/rules/no-historical-clutter.md +7 -49
- package/rules/no-inline-destructive-literals.md +3 -5
- package/rules/package-inventory-stale-entry.md +7 -32
- package/rules/re-stage-before-commit.md +6 -23
- package/rules/shell-invocation-policy.md +1 -1
- package/rules/vault-context.md +3 -3
- package/rules/verified-commit-gate-skip.md +28 -0
- package/rules/workers-done-before-complete.md +2 -30
- package/scripts/claude_chain_runner.py +39 -3
- package/scripts/dev_env_scripts_constants/claude_chain_constants.py +3 -0
- package/scripts/test_claude_chain_runner.py +112 -0
- package/scripts/test_grok_headless_runner.py +0 -1
- package/skills/auditing-claude-config/CLAUDE.md +2 -1
- package/skills/auditing-claude-config/SKILL.md +114 -176
- package/skills/auditing-claude-config/reference/probe-hook.md +74 -0
- package/skills/autoconverge/CLAUDE.md +1 -0
- package/skills/autoconverge/SKILL.md +310 -346
- package/skills/autoconverge/reference/CLAUDE.md +1 -0
- package/skills/autoconverge/reference/copilot-findings.md +51 -0
- package/skills/closeout/SKILL.md +7 -9
- package/skills/copilot-finding-triage/SKILL.md +5 -7
- package/skills/copilot-review/CLAUDE.md +3 -2
- package/skills/copilot-review/SKILL.md +119 -155
- package/skills/copilot-review/templates/subagent-prompt.md +49 -0
- package/skills/everything-search/SKILL.md +7 -1
- package/skills/fresh-branch/CLAUDE.md +6 -9
- package/skills/fresh-branch/SKILL.md +84 -33
- package/skills/fresh-branch/scripts/create_fresh_branch.py +445 -0
- package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/__init__.py +1 -0
- package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +74 -0
- package/skills/fresh-branch/scripts/test_create_fresh_branch.py +670 -0
- package/skills/orchestrator/SKILL.md +161 -147
- package/skills/pr-converge/SKILL.md +277 -422
- package/skills/pr-converge/reference/CLAUDE.md +1 -0
- package/skills/pr-converge/reference/progress-checklist.md +168 -0
- package/skills/pr-converge/test_step5_host_branch.py +8 -6
- package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
- package/skills/privacy-hygiene/SKILL.md +68 -115
- package/skills/privacy-hygiene/reference/sweep-procedure.md +62 -0
- package/skills/session-log/CLAUDE.md +2 -1
- package/skills/session-log/SKILL.md +4 -26
- package/skills/session-log/templates/frontmatter.md +40 -0
- package/skills/skill-builder/CLAUDE.md +8 -7
- package/skills/skill-builder/SKILL.md +26 -11
- package/skills/skill-builder/references/CLAUDE.md +3 -1
- package/skills/skill-builder/references/delegation-map.md +21 -12
- package/skills/skill-builder/references/description-field.md +9 -11
- package/skills/skill-builder/references/deterministic-elements.md +218 -0
- package/skills/skill-builder/references/self-audit-checklist.md +62 -45
- package/skills/skill-builder/references/skill-modularity.md +8 -9
- package/skills/skill-builder/templates/CLAUDE.md +2 -2
- package/skills/skill-builder/templates/gap-analysis.md +15 -0
- package/skills/skill-builder/workflows/CLAUDE.md +5 -5
- package/skills/skill-builder/workflows/improve-skill.md +18 -9
- package/skills/skill-builder/workflows/new-skill.md +23 -15
- package/skills/skill-builder/workflows/polish-skill.md +28 -21
- package/system-prompts/CLAUDE.md +3 -3
- package/docs/agents-md-alignment-plan.md +0 -123
- package/docs/emotion-informed-prompt-design.md +0 -362
- package/rules/es-exe-file-search.md +0 -17
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Behavioral tests for the claude fallback-chain runner."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import os
|
|
4
5
|
import subprocess
|
|
5
6
|
import sys
|
|
6
7
|
from pathlib import Path
|
|
@@ -78,20 +79,32 @@ class _Recorder:
|
|
|
78
79
|
self.behavior_by_command = behavior_by_command
|
|
79
80
|
self.invocations = []
|
|
80
81
|
self.timeouts = []
|
|
82
|
+
self.all_keyword_arguments = []
|
|
81
83
|
|
|
82
84
|
def __call__(self, invocation, **keyword_arguments):
|
|
83
85
|
self.invocations.append(invocation)
|
|
84
86
|
self.timeouts.append(keyword_arguments.get("timeout"))
|
|
87
|
+
self.all_keyword_arguments.append(keyword_arguments)
|
|
85
88
|
behavior = self.behavior_by_command[invocation[0]]
|
|
86
89
|
if isinstance(behavior, BaseException):
|
|
87
90
|
raise behavior
|
|
88
91
|
return behavior
|
|
89
92
|
|
|
90
93
|
|
|
94
|
+
class _TtyStdin:
|
|
95
|
+
def isatty(self) -> bool:
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _install_tty_stdin(monkeypatch):
|
|
100
|
+
monkeypatch.setattr(runner.sys, "stdin", _TtyStdin())
|
|
101
|
+
|
|
102
|
+
|
|
91
103
|
def _install(monkeypatch, config_file, behavior_by_command):
|
|
92
104
|
recorder = _Recorder(behavior_by_command)
|
|
93
105
|
monkeypatch.setattr(runner, "chain_config_path", lambda: config_file)
|
|
94
106
|
monkeypatch.setattr(runner, "chain_subprocess_runner", recorder)
|
|
107
|
+
_install_tty_stdin(monkeypatch)
|
|
95
108
|
return recorder
|
|
96
109
|
|
|
97
110
|
|
|
@@ -276,6 +289,45 @@ def test_extra_args_are_appended_to_invocation(
|
|
|
276
289
|
assert recorder.invocations[0] == ["claude", "-p", "hello", "--account", "ev"]
|
|
277
290
|
|
|
278
291
|
|
|
292
|
+
def test_run_claude_forwards_stdin_text_to_subprocess(
|
|
293
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
294
|
+
) -> None:
|
|
295
|
+
config_file = _write_chain_config(tmp_path, [_entry("claude")])
|
|
296
|
+
recorder = _install(monkeypatch, config_file, {"claude": _completed("claude", 0)})
|
|
297
|
+
runner.run_claude(
|
|
298
|
+
_PROMPT_ARGUMENTS, timeout_seconds=5, stdin_text="charter body"
|
|
299
|
+
)
|
|
300
|
+
assert recorder.all_keyword_arguments[0]["input"] == "charter body"
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def test_run_claude_passes_none_input_when_stdin_text_omitted(
|
|
304
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
305
|
+
) -> None:
|
|
306
|
+
config_file = _write_chain_config(tmp_path, [_entry("claude")])
|
|
307
|
+
recorder = _install(monkeypatch, config_file, {"claude": _completed("claude", 0)})
|
|
308
|
+
runner.run_claude(_PROMPT_ARGUMENTS, timeout_seconds=5)
|
|
309
|
+
assert recorder.all_keyword_arguments[0].get("input") is None
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def test_cli_forwards_piped_stdin_to_invocation(
|
|
313
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
314
|
+
) -> None:
|
|
315
|
+
config_file = _write_chain_config(tmp_path, [_entry("claude")])
|
|
316
|
+
recorder = _install(monkeypatch, config_file, {"claude": _completed("claude", 0)})
|
|
317
|
+
|
|
318
|
+
class _PipedStdin:
|
|
319
|
+
def isatty(self) -> bool:
|
|
320
|
+
return False
|
|
321
|
+
|
|
322
|
+
def read(self) -> str:
|
|
323
|
+
return "charter body"
|
|
324
|
+
|
|
325
|
+
monkeypatch.setattr(runner.sys, "stdin", _PipedStdin())
|
|
326
|
+
exit_code = runner.main([CLI_ARGUMENTS_SEPARATOR, "-p", "hi"])
|
|
327
|
+
assert exit_code == 0
|
|
328
|
+
assert recorder.all_keyword_arguments[0]["input"] == "charter body"
|
|
329
|
+
|
|
330
|
+
|
|
279
331
|
def test_signature_matching_is_case_insensitive(
|
|
280
332
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
281
333
|
) -> None:
|
|
@@ -362,6 +414,7 @@ def test_cli_missing_config_exits_config_error(
|
|
|
362
414
|
) -> None:
|
|
363
415
|
missing_config = tmp_path / CONFIG_FILENAME
|
|
364
416
|
monkeypatch.setattr(runner, "chain_config_path", lambda: missing_config)
|
|
417
|
+
_install_tty_stdin(monkeypatch)
|
|
365
418
|
exit_code = runner.main([CLI_ARGUMENTS_SEPARATOR, "-p", "hi"])
|
|
366
419
|
captured = capsys.readouterr()
|
|
367
420
|
assert exit_code == CHAIN_CONFIG_ERROR_EXIT_CODE
|
|
@@ -470,3 +523,62 @@ def test_chain_config_path_points_at_home_config() -> None:
|
|
|
470
523
|
config_path = runner.chain_config_path()
|
|
471
524
|
assert config_path.name == CONFIG_FILENAME
|
|
472
525
|
assert CLAUDE_HOME_SUBDIRECTORY in config_path.parts
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def test_real_subprocess_capture_replaces_undecodable_bytes(
|
|
529
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
530
|
+
) -> None:
|
|
531
|
+
config_file = _write_chain_config(tmp_path, [_entry(sys.executable)])
|
|
532
|
+
monkeypatch.setattr(runner, "chain_config_path", lambda: config_file)
|
|
533
|
+
child_code = 'import sys; sys.stdout.buffer.write(b"ok \\x90 end")'
|
|
534
|
+
chain_result = runner.run_claude(["-c", child_code], timeout_seconds=60)
|
|
535
|
+
assert chain_result.served_command == sys.executable
|
|
536
|
+
assert chain_result.returncode == 0
|
|
537
|
+
assert chain_result.stdout == "ok � end"
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def test_real_subprocess_capture_preserves_utf8_text(
|
|
541
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
542
|
+
) -> None:
|
|
543
|
+
config_file = _write_chain_config(tmp_path, [_entry(sys.executable)])
|
|
544
|
+
monkeypatch.setattr(runner, "chain_config_path", lambda: config_file)
|
|
545
|
+
child_code = 'import sys; sys.stdout.buffer.write("report ✅ done".encode("utf-8"))'
|
|
546
|
+
chain_result = runner.run_claude(["-c", child_code], timeout_seconds=60)
|
|
547
|
+
assert chain_result.served_command == sys.executable
|
|
548
|
+
assert chain_result.returncode == 0
|
|
549
|
+
assert chain_result.stdout == "report ✅ done"
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def test_cli_emits_utf8_when_console_encoding_is_legacy(tmp_path: Path) -> None:
|
|
553
|
+
tmp_home = tmp_path / "home"
|
|
554
|
+
claude_directory = tmp_home / CLAUDE_HOME_SUBDIRECTORY
|
|
555
|
+
claude_directory.mkdir(parents=True)
|
|
556
|
+
config_file = claude_directory / CONFIG_FILENAME
|
|
557
|
+
config_file.write_text(
|
|
558
|
+
json.dumps({CONFIG_CHAIN_KEY: [_entry(sys.executable)]}),
|
|
559
|
+
encoding=UTF8_ENCODING,
|
|
560
|
+
)
|
|
561
|
+
runner_script = _SCRIPTS_DIR / "claude_chain_runner.py"
|
|
562
|
+
child_code = 'import sys; sys.stdout.buffer.write("report ✅".encode("utf-8"))'
|
|
563
|
+
legacy_console_encoding = "cp1252"
|
|
564
|
+
child_environment = dict(os.environ)
|
|
565
|
+
child_environment["USERPROFILE"] = str(tmp_home)
|
|
566
|
+
child_environment["HOME"] = str(tmp_home)
|
|
567
|
+
child_environment["PYTHONIOENCODING"] = legacy_console_encoding
|
|
568
|
+
child_environment.pop("PYTHONUTF8", None)
|
|
569
|
+
runner_command = [
|
|
570
|
+
sys.executable,
|
|
571
|
+
str(runner_script),
|
|
572
|
+
CLI_ARGUMENTS_SEPARATOR,
|
|
573
|
+
"-c",
|
|
574
|
+
child_code,
|
|
575
|
+
]
|
|
576
|
+
completed = subprocess.run(
|
|
577
|
+
runner_command,
|
|
578
|
+
capture_output=True,
|
|
579
|
+
env=child_environment,
|
|
580
|
+
timeout=60,
|
|
581
|
+
)
|
|
582
|
+
assert completed.returncode == 0
|
|
583
|
+
assert b"Traceback" not in completed.stderr
|
|
584
|
+
assert "report ✅" in completed.stdout.decode(UTF8_ENCODING)
|
|
@@ -418,7 +418,6 @@ def test_tightened_auth_phrases_still_classify_auth_failure(
|
|
|
418
418
|
assert outcome.classification == CLASSIFICATION_AUTH_FAILURE, each_stderr
|
|
419
419
|
|
|
420
420
|
|
|
421
|
-
def test_dual_match_prefers_auth_failure_over_usage_limit(
|
|
422
421
|
def test_missing_binary_returns_dedicated_error(
|
|
423
422
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
424
423
|
) -> None:
|
|
@@ -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
|
|
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 (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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
from
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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.
|
|
@@ -21,6 +21,7 @@ Drives one draft PR to convergence in a single autonomous workflow run. Each rou
|
|
|
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. |
|